SqlQuery

Sağladığı Fayda ve Kolaylıklar

Öncelikle bu nesneyi kullanmanın bize sağlayacağı bazı avantajları sıralayalım:

Basit Bir SELECT Sorgusu

  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public static string SimpleSelectFromOrderBy()
  8. {
  9. var query = new SqlQuery();
  10. query.Select("Firstname");
  11. query.Select("Surname");
  12. query.From("People");
  13. query.OrderBy("Age");
  14. return query.ToString();
  15. }
  16. }
  17. }

Programı çalıştırdığımızda fonksiyon aşağıdaki gibi bir sonuç verir:

  1. SELECT Firstname, Surname FROM People ORDER BY Age

İlk satırda SqlQuery nesnemizi yegane parametresiz constructor’ı ile oluşturduk. Eğer bu noktada ToString() metodunu çağırsaydık aşağıdaki gibi bir sonuç alacaktık:

  1. SELECT FROM

SqlQuery sorgunuza herhangi bir doğrulama yapmaz. Sadece çağrılarınızla ürettiğiniz sorguyu metne çevirir. SELECT ve FROM deyimleri siz hiçbir alan seçmeseniz de, tablo adı belirtmeseniz de metinsel gösterimde yukarıdaki gibi çıkacaktır. SqlQuery bu deyimleri içermeyen bir sorgu üretemez.

Ardından Select metodunu “Firstname” parametresiyle çağırdık. Sorgumuz aşağıdaki hale geldi:

  1. SELECT Firstname FROM

Yine Select metodunu, bu sefer “Surname” parametresiyle çağırınca, SqlQuery bir önceki seçilen alan ile “Surname” arasına virgül koyarak sorguyu aşağıdaki gibi üretti:

  1. SELECT Firstname, Surname FROM

From ve OrderBy metodlarını da çağırarak sorgumuza nihai halini verdik:

  1. SELECT Firstname, Surname FROM People ORDER BY Age

Metod Çağırım Sırası ve Etkisi

Örnek koddaki “From”, “OrderBy” ve “Select” içeren satırları hangi sırada yazarsak yazalım sonuç değişmeyecekti. Ancak aşağıdaki gibi Select çağrılarının sırası değişirse yani Surname alanını Firstname’den önce seçseydik…

  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public static string ReorderedQuery()
  8. {
  9. var query = new SqlQuery();
  10. query.OrderBy("Age");
  11. query.Select("Surname");
  12. query.From("People");
  13. query.Select("Firstname");
  14. return query.ToString();
  15. }
  16. }
  17. }

…sonuçta, sadece alanların SELECT ifadesinde görünme sıraları değişecekti:

  1. SELECT Surname, Firstname FROM People ORDER BY Age

Zincirleme Metod Çağrımı (Method Chaining)

Yukarıdaki kod listelerine dikkatli bakınca sorgumuzu düzenlediğimiz her satıra “query.” ile başladığımızı ve bunun göze pek de hoş gelmediğini farkedebilirsiniz.

SqlQuery’nin, method chaining (zincirleme metod çağrımı) özelliğinden faydalanarak, bu sorguyu daha okunaklı ve kolay bir şekilde yazabiliriz:

  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public static string MethodChaining()
  8. {
  9. var query = new SqlQuery()
  10. .Select("Firstname")
  11. .Select("Surname")
  12. .From("People")
  13. .OrderBy("Age");
  14. return query.ToString();
  15. }
  16. }
  17. }

jQuery yada LINQ kullanıyorsanız bu zincirleme çağırım şekline aşina olmalısınız.

İstersek “query” yerel değişkenininden de kurtulabiliriz:

  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public static string SimpleSelectRemoveVariable()
  8. {
  9. return new SqlQuery()
  10. .Select("Firstname")
  11. .Select("Surname")
  12. .From("People")
  13. .OrderBy("Age")
  14. .ToString();
  15. }
  16. }
  17. }

Select Metodu

  1. public SqlQuery Select(string expression)

Şu ana kadar verdiğimiz örneklerde, Select metodunun yukarıdaki overload’ını kullandık. Expression (ifade) parametresi, bir alan adı ya da (Adi + Soyadi) gibi bir SQL ifadesi olabilir. Bu metodu her çağırdığınızda sorgunun SELECT listesine, verdiğiniz alan adı ya da ifade eklenir (araya virgül konarak).

Tek bir çağırımda, birden fazla alan seçmek isterseniz, şu overload’ı kullanabilirsiniz:

  1. public SqlQuery Select(params string[] expressions)

Örneğin:

  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public static string SelectMultipleFieldsInOneCall()
  8. {
  9. return new SqlQuery()
  10. .Select("Firstname", "Surname", "Age", "Gender")
  11. .From("People")
  12. .ToString();
  13. }
  14. }
  15. }
  1. SELECT Firstname, Surname, Age, Gender FROM People

Seçtiğiniz bir alana kısa ad (column alias) atamak isterseniz SelectAs metodundan faydalanabilirsiniz:

  1. public SqlQuery SelectAs(string expression, string alias)
  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public static string SelectAs()
  8. {
  9. return new SqlQuery()
  10. .SelectAs("(Firstname + Surname)", "Fullname")
  11. .From("People")
  12. .ToString();
  13. }
  14. }
  15. }
  1. SELECT (Firstname + Surname) Fullname FROM People

From Metodu

  1. public SqlQuery From(string table)

SqlQuery’nin From metodu, sorgunun FROM ifadesini üretmek için en az (ve genellikle) bir kez çağrılmalıdır. İlk çağırdığınızda sorgunuzun ana tablo ismini belirlemiş olursunuz.

İkinci bir kez çağırırsanız, verdiğiniz tablo ismi, sorgunun FROM kısmına, asıl tablo adıyla aralarına virgül konarak eklenir. Bu durumda da CROSS JOIN yapmış olursunuz.

  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public static string CrossJoinWithFrom()
  8. {
  9. return new SqlQuery()
  10. .Select("Firstname")
  11. .Select("Surname")
  12. .From("People")
  13. .From("City")
  14. .From("Country")
  15. .OrderBy("Age")
  16. .ToString();
  17. }
  18. }
  19. }

Oluşan sorgu aşağıdaki gibi olacaktır:

  1. SELECT Firstname, Surname FROM People, City, Country ORDER BY Age

Alias Nesnesi ve SqlQuery ile Kullanımı

Sorgularımız uzadıkça ve içindeki JOIN sayısı arttıkça, hem alan adı çakışmalarını engellemek, hem de istenen alanlara daha kolay ulaşmak için, kullandığımız tablolara aşağıdaki gibi kısa adlar (alias) vermeye başlarız.

  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public static string FromWithStringAliases()
  8. {
  9. return new SqlQuery()
  10. .Select("p.Firstname")
  11. .Select("p.Surname")
  12. .Select("p.CityName")
  13. .Select("p.CountryName")
  14. .From("Person p")
  15. .From("City c")
  16. .From("Country o")
  17. .OrderBy("p.Age")
  18. .ToString();
  19. }
  20. }
  21. }
  1. SELECT p.Firstname, p.Surname, c.CityName, o.CountryName FROM People p, City c, Country o ORDER BY p.Age

Görüleceği üzere alanları seçerken de başlarına tablolarına atadığımız kısa adları (p.Surname gibi) getirdik. Bu sayede tablolardaki alan isimleri çakışsa da (aynı alan adı People, City, Country tablolarında olsa da) sorun çıkmasını engellemiş olduk.

Sorgu içinde kullandığımız bu kısa adları, dilersek SqlQuery ile birlikte kullanabileceğimiz Alias nesnesleri olarak ta tanımlayabiliriz.

  1. var p = new Alias("Person", "p");

Alias aslında bir string e verdiğiniz ad gibidir. Fakat string ten farklı olarak, SQL ifadelerinde kullanacabileceğimiz “kısa ad”.”alan adı” şeklinde metinleri, “+” operatörü aracılığıyla üretmemize yardımcı olur:

  1. p + "Surname"

p.Surname

Bu işlem C#’ın “+” operatörünün overload edilmesi sayesinde gerçekleşmektedir. Bir alias’ı bir alan adı ile topladığınızda, alias’ın kısa adı ve alan adı, aralarına “.” konarak birleştirilir

ne yazık ki C#’ın member access operatorünü (“.”) overload edemiyoruz, bu yüzden “+” kullanılmak durumunda.

Sorgumuzu Alias nesnesinden faydalanarak düzenleyelim:

  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public static string FromUsingAlias()
  8. {
  9. var p = new Alias("People", "p");
  10. var c = new Alias("City", "c");
  11. var o = new Alias("Country", "o");
  12. return new SqlQuery()
  13. .Select(p + "Firstname")
  14. .Select(p + "Surname")
  15. .Select(c + "CityName")
  16. .Select(o + "CountryName")
  17. .From(p)
  18. .From(c)
  19. .From(o)
  20. .OrderBy(p + "Age")
  21. .ToString();
  22. }
  23. }
  24. }
  1. SELECT p.Firstname, p.Surname, c.CityName, o.CountryName FROM People p, City c, Country o ORDER BY p.Age

Görüldüğü gibi sonuç aynı, ancak yazdığımız kod biraz daha uzadı. Peki Alias nesnesi kullanmak burada bize ne kazandırdı?

Şu haliyle değerlendirildiğinde avantajı ilk bakışta görülmeyebilir. Ancak aşağıdaki koddaki gibi alan isimlerini tutan sabitlerimiz olsaydı…

  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. const string Firstname = "Firstname";
  8. const string Surname = "Surname";
  9. const string Age = "Age";
  10. const string CityName = "CityName";
  11. const string CountryName = "CountryName";
  12. public static string UsingFieldNameConsts()
  13. {
  14. var p = new Alias("People", "p");
  15. var c = new Alias("City", "c");
  16. var o = new Alias("Country", "o");
  17. return new SqlQuery()
  18. .Select(p + Firstname)
  19. .Select(p + Surname)
  20. .Select(c + CityName)
  21. .Select(o + CountryName)
  22. .From(p)
  23. .From(c)
  24. .From(o)
  25. .OrderBy(p + Age)
  26. .ToString();
  27. }
  28. }
  29. }

…Visual Studio’nun IntelliSense özelliğinden daha çok faydalanmış ve birçok yazım hatasını derleme anında yakalamış olabilirdik.

Tabi örnekteki gibi, her sorguyu yazmadan önce, alan isimlerini sabit olarak tanımlamak çok ta mantıklı ve kolay değil. Dolayısıyla bunların merkezi bir yerde tanımlanmış olması lazım (hatta, direk entity tanımımızdan bulunması, ki buna daha sonra değineceğiz)

Tabloya özel alan adı tanımlarınızı, entity yapısı kullanmayacaksanız, aşağıdaki örnekteki gibi de yapabilirsiniz:

  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public class PeopleAlias : Alias
  8. {
  9. public PeopleAlias(string alias)
  10. : base(alias)
  11. {
  12. }
  13. public string ID { get { return this["ID"]; } }
  14. public string Firstname { get { return this["Firstname"]; } }
  15. public string Surname { get { return this["Surname"]; } }
  16. public string Age { get { return this["Age"]; } }
  17. }
  18. public class CityAlias : Alias
  19. {
  20. public CityAlias(string alias)
  21. : base(alias)
  22. {
  23. }
  24. public string ID { get { return this["ID"]; } }
  25. public string CountryID { get { return this["CountryID"]; } }
  26. public string CityName { get { return this["CityName"]; } }
  27. }
  28. public class CountryAlias : Alias
  29. {
  30. public CountryAlias(string alias)
  31. : base(alias)
  32. {
  33. }
  34. public string ID { get { return this["ID"]; } }
  35. public string CountryName { get { return this["CountryName"]; } }
  36. }
  37. public static string UsingPredefinedClasses()
  38. {
  39. var p = new PeopleAlias("p");
  40. var c = new CityAlias("c");
  41. var o = new CountryAlias("o");
  42. return new SqlQuery()
  43. .Select(p.Firstname)
  44. .Select(p.Surname)
  45. .Select(c.CityName)
  46. .Select(o.CountryName)
  47. .From(p)
  48. .From(c)
  49. .From(o)
  50. .OrderBy(p.Age)
  51. .ToString();
  52. }
  53. }
  54. }

Bu sayede alan adlarını tutan sınıflarımızı bir kez tanımladık ve tüm sorgularımızda kolaylıkla kullanabileceğiz.

Yukarıdaki örneklerde ayrıca SqlQuery.From’un aşağıdaki gibi Alias parametresi alan overload’ından faydalandık:

  1. public SqlQuery From(Alias alias)

Bu fonksiyon çağrıldığında, SQL sorgusunun FROM ifadesine, alias oluşturulurken tanımlanan tablo, kısa adıyla birlikte eklenir.

Eğer alias’ınızı oluştururken bir tablo adı belirtmediyseniz (new Alias(“c”) gibi) şu overload’ı kullanabilirsiniz:

  1. public SqlQuery From(string table, Alias alias)

With Uzantı (Extension) Metodu

Örneğimizde, kısa adlarımızı (alias) önceden değişken olarak tanımlamamız gerekli oldu. Eğer bunu SQL query’nin zincirleme akışını bozmadan yapmak isteseydik, With extension metodundan faydalanabilirdik:

  1. public static string UsingWithExtension()
  2. {
  3. return new SqlQuery().With(
  4. new Alias("People", "p"),
  5. new Alias("City", "c"),
  6. new Alias("Country", "o"),
  7. (me, p, c, o) => me
  8. .Select(p + Firstname)
  9. .Select(p + Surname)
  10. .Select(c + CityName)
  11. .Select(o + CountryName)
  12. .From(p)
  13. .From(c)
  14. .From(o)
  15. .OrderBy(p + Age))
  16. .ToString();
  17. }

ChainingExtensions altında statik bir uzantı (extension) metodu olarak tanımlanmış olan With metodu, sorgunuzun akışını bozmadan, inline olarak değişkenler tanımlamanıza imkan verir. With metoduna geçirdiğiniz parametreler, son parametre verdiğiniz lambda metoduna, sırasıyla geçirilir. Metoda ilk geçirilen parametre (me) ise sorgunun kendisidir.

Inline değişken tanımlamak, sorgumuzu daha akışkan hale getirse de, bir miktar okunurluluğu azaltmış olabilir. Fonksiyonalite ve akıcılık arasında ne tercih yapacağına siz karar vermelisiniz.

OrderBy Metodu

  1. public SqlQuery OrderBy(string expression, bool desc = false)

OrderBy metodu da Select gibi bir alan adı ya da ifadesiyle çağrılabilir. “Desc” opsiyonel parametresine true atarsanız, alan adı ya da ifadenizin sonuna “ DESC” getirilir.

OrderBy metodu, verdiğiniz ifadeleri ORDER BY deyiminin sonuna ekler. Bazen alanı listenin başına getirmek te isteyebiliriz. Örneğin çeşitli alanlara göre sıralanmış bir sorgu hazırladıktan sonra, kullanıcı arayüzünde tıklanan kolona göre sıralamanın değişmesi (önceki sıralamayı tümüyle kaybetmeden) gerekebilir. Bunun için SqlQuery, OrderByFirst metodunu sağlar:

  1. public SqlQuery OrderByFirst(string expression, bool desc = false)
  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public static string OrderByFirst()
  8. {
  9. var query = new SqlQuery()
  10. .Select("Firstname")
  11. .Select("Surname")
  12. .From("Person")
  13. .OrderBy("PersonID");
  14. return query.OrderByFirst("Age")
  15. .ToString();
  16. }
  17. }
  18. }
  1. SELECT Firstname, Surname FROM Person ORDER BY Age, PersonID

Order by kullanmış olsaydık şunu elde edecektik:

  1. SELECT Firstname, Surname FROM Person ORDER BY PersonID, Age

Distinct Metodu

  1. public SqlQuery Distinct(bool distinct)

DISTINCT deyimini içeren bir sorgu üretmek istediğinizde bu metodu kullanabilirsiniz:

  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public static string DistinctMethod()
  8. {
  9. return new SqlQuery()
  10. .From("Person")
  11. .Distinct(true)
  12. .Select("Firstname")
  13. .ToString();
  14. }
  15. }
  16. }
  1. SELECT DISTINCT Firstname FROM Person

Group By Metodu

  1. public SqlQuery GroupBy(string expression)

GroupBy metodu bir alan adı ya da ifadesiyle çağrılır ve bu ifadeyi sorgunun GROUP BY deyiminin sonuna ekler.

  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public static string GroupByMethod()
  8. {
  9. new SqlQuery()
  10. .From("Person")
  11. .Select("Firstname", "Lastname", "Count(*)")
  12. .GroupBy("Firstname")
  13. .GroupBy("LastName")
  14. .ToString();
  15. }
  16. }
  17. }
  1. SELECT Firstname, Lastname, Count(*) FROM Person GROUP BY Firstname, LastName

Having Metodu

  1. public SqlQuery Having(string expression)

GroupBy metodu ile birlikte kullanabileceğiniz Having metodu, mantıksal bir ifadeyle çağrılır ve bu ifadeyi sorgunun HAVING deyiminin sonuna ekler.

  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public static string HavingMethod()
  8. {
  9. new SqlQuery()
  10. .From("Person")
  11. .Select("Firstname", "Lastname", "Count(*)")
  12. .GroupBy("Firstname")
  13. .GroupBy("LastName")
  14. .Having("Count(*) > 5")
  15. .ToString();
  16. }
  17. }
  18. }
  1. SELECT Firstname, Lastname, Count(*) FROM Person GROUP BY Firstname, LastName HAVING Count(*) > 5

Sayfalama İşlemleri (SKIP / TAKE / TOP / LIMIT)

  1. public SqlQuery Skip(int skipRows)
  2. public SqlQuery Take(int rowCount)

SqlQuery, LINQ’de Take ve Skip olarak geçen sayfalama metodlarını destekler. Bunlardan Take, Microsoft SQL Server’da TOP’a karşılık gelirken, SKIP’in direk bir karşılığı olmadığından SqlQuery, ROW_NUMBER() fonksiyonundan faydalanır. Bu nedenle SQL server için SKIP kullanmak istediğinizde, sorgunuzda en az bir ORDER BY ifadesinin de olması gerekir.

  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public static string SkipTakePaging()
  8. {
  9. new SqlQuery()
  10. .From("Person")
  11. .Select("Firstname", "Lastname")
  12. .OrderBy("PersonId")
  13. .Skip(300)
  14. .Take(100)
  15. .ToString();
  16. }
  17. }
  18. }
  1. SELECT * FROM (
  2. SELECT TOP 400 Firstname, Lastname, ROW_NUMBER() OVER (ORDER BY PersonId) AS _row_number_ FROM Person
  3. ) _row_number_results_ WHERE _row_number_ > 300

Farklı Veritabanları Desteği

Sayfalama işlemlerindeki örneğimizde, SqlQuery, Microsoft SQL Server 2008’e uygun bir sayfalama metodu kullandı.

Dialect metodu ile SQL query nin kullandığı DIALECT ya da sunucu tipini değiştirebiliriz:

  1. public SqlQuery Dialect(SqlDialect dialect)

Şu an için aşağıdaki sunucu tipleri desteklenmektedir:

  1. [Flags]
  2. public enum SqlDialect
  3. {
  4. MsSql = 1,
  5. Firebird = 2,
  6. UseSkipKeyword = 512,
  7. UseRowNumber = 1024,
  8. UseOffsetFetch = 2048,
  9. MsSql2005 = MsSql | UseRowNumber,
  10. MsSql2012 = MsSql | UseOffsetFetch | UseRowNumber
  11. }

Örneğin SqlDialect.MsSql2012’yi seçip, SQL Server 2012 ile gelen OFFSET FETCH deyimlerinden faydalanmak isteseydik:

  1. namespace Samples
  2. {
  3. using Serenity;
  4. using Serenity.Data;
  5. public partial class SqlQuerySamples
  6. {
  7. public static string SkipTakePaging()
  8. {
  9. new SqlQuery()
  10. .Dialect(SqlDialect.MsSql2012)
  11. .From("Person")
  12. .Select("Firstname", "Lastname")
  13. .OrderBy("PersonId")
  14. .Skip(300)
  15. .Take(100)
  16. .ToString();
  17. }
  18. }
  19. }
  1. SELECT Firstname, Lastname FROM Person ORDER BY PersonId
  2. OFFSET 300 ROWS FETCH NEXT 100 ROWS ONLY

SqlDialect.Firebird’ü tercih etseydik şu şekilde bir sorgu oluşurdu:

  1. SELECT FIRST 100 SKIP 300 Firstname, Lastname FROM Person ORDER BY PersonId

SqlQuery’nin Sunucu/Dialect desteği henüz mükemmel olmasa da, temel işlemlerde sorun çıkarmayacak düzeydedir.

Uygulamanızda tek tipte sunucu kullanıyorsanız, her sorgunuzda, dialect ayarlamak istemeyebilirsiniz. Bunun için SqlSettings.CurrentDialect’i değiştirmelisiniz. Örneğin aşağıdaki kodu program başlangıcında, ya da global.asax dosyanızdan çağırabilirsiniz:

  1. SqlSettings.CurrentDialect = SqlDialect.MsSql2012;