C# Entity Framework 6.0’da Linq İle Tüyolar
Selamlar,
Bu makalede Linq ile işinize yaracak ya da gözden kaçırmış olabileceğiniz, birkaç konudan bahsetmek istiyorum.
1-) IEnumerable<T> query:
- Aşağıdaki kod satırnın çıktısı ne olur ? Sonuç null. Neden, çünkü “IEnumerable” query hemen çalıştırılmaz. “numbers.Clear()” ile dizi temizlendikten sonra, “foreach” içinde çağrıldığı için, null değer döner.
1 2 3 4 5 |
var numbers =new List<int>(){1,2}; IEnumerable<int> query= numbers.Select(n=>n*10); numbers.Clear(); Console.WriteLine("IEnumerable:"); foreach(int n in query) Console.WriteLine(n+"|"); |
Çıktı Ekranı:
- Peki aynı sorguyu aşağıdaki gibi List<T> ile yapılsa idi, sonuc ne olurdu? “List<T>()” sorguları hemen işletildiği için, sorgu sonucu listenin silinmesinden etkilenmez ve sonuçlar gelirdi.
1 2 3 4 5 |
var numbers2 =new List<int>(){1,2}; List<int> timesTen = numbers2.Select(n=>n*10).ToList(); numbers2.Clear(); Console.WriteLine("List:"); foreach(int n in timesTen) Console.WriteLine(n+"|"); |
Çıktı Ekranı:
- Son olarak, aşağıdaki sorgunun sonucu ne olurdu? IEnumerable, gerçekten çağrıldıktan sonra çalıştırıldığı için, sorguya sonradan müdahale edilebilmiş ve “factor” katsayısının ilk hali ile değil, son hali ile ilgili sorgu çağrılmıştır.
1 2 3 4 5 6 7 |
var numbers =new List<int>(){1,2}; int factor=10; IEnumerable<int> query= numbers.Select(n=>n*factor); factor =20; Console.WriteLine("IEnumerable:"); foreach(int n in query) Console.WriteLine(n+"|"); |
Çıktı Ekranı:
- Aşağıda görüldüğü gibi Linq ile “where” koşulları peş peşe eklenerek ilgili sorgu çağrılabilir. Amaç cümle içinden “a,t,u,l,e” harflerinin çıkarılmasıdır.
1 2 3 4 5 6 7 8 |
IEnumerable<char> query= "Cut the night with the light"; query = query.Where(c=> c !='a'); query = query.Where(c=> c !='t'); query = query.Where(c=> c !='u'); query = query.Where(c=> c !='l'); query = query.Where(c=> c !='e'); foreach (char c in query) Console.Write(c); |
Çıktı Ekranı:
Pratik Yol: Eğer aşağıdaki gibi bir sorgu yazılır ise de, yine aynı sonuç alınır. “Where”‘ler peş peşe sona eklenir. Ve ilgili query en son çalıştırılarak, yine aynı sonuç alınır.
Not: ” query = query.Where( c => c != excepts[i] ) ” Şeklinde kod yazılsa idi, “i” değeri son olarak “5” değerini alacağı ve query enson foreach anında çağrılacağı için, “IndexOutOfRangeException” hatası alınacakdı. Çünkü excepts[5] dizisinin, 5. elemanı yok.
1 2 3 4 5 6 7 8 |
IEnumerable<char> query = "Cut the night with the light"; string excepts ="atule"; for(int i=0; i<excepts.Length; i++){ char except =excepts[i]; query = query.Where(c => c != except); } foreach (char c in query) Console.Write(c); |
Çıktı Ekranı:
2-) Anonymous Types ve “into” Keyword:
Aşağıdaki örnekde görüldüğü gibi, “names” dizisi üzerinde “select new{ }” ile anonymous tipinde “Original” ve “Change” propertylerine sahip bir Liste üzerinde işlem yapılarak, “into” keyword’ü ile “temp” değişkenine atanmış ve ek filitrelemeler, bu atanan “temp” üzerinden yapılmıştır.
Kısaca bir liste üzerinde, kendisi ve üzerinde işlem yapılacak eşleniği şeklinde 2 property olarak oluşturulmuş , eşleneği üzerinde çeşitli işlemler yapılıp, filitreden geçirilmiştir. Bu filitreleri geçen elemanların da orginal hali, ekrana bastırılmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
string[] names = {"Bora", "Engin", "Selcuk", "Burak", "Veli"}; var query = from n in names select new { Original = n, Change = n.Replace("a", "").Replace("e", "").Replace("i", "").Replace("o", "").Replace("u", "") } into temp where temp.Change.Length > 2 select temp.Original; foreach (string c in query) Console.WriteLine(c); |
Çıktı Ekranı:
3-) Interpreted and Local Queryler:
- Aşağıdaki örnekde, custom yazılan bir filter’ın, ilgli linq query’e nasıl ekleyebileceği görülmektedir. Pair() “IEnumerable<string>” Extension methodu bize, gelen sorgu sonucunu ikili eşleştirerek geriye dönmektedir. Bu şekilde normal Linq query’e, kendi sorgularımızı attach etmiş oluruz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
using System; using System.Linq; using System.Collections.Generic; public static class Program { public static void Main() { var persons = new List<string>() {"Bora", "Engin", "Selcuk", "Burak", "Veli"}; IEnumerable<string> q = persons.Select(p => p.ToUpper()).OrderBy(n => n).Pair().Select((n, i) => "Pair " + i.ToString() + " = " + n); foreach (string element in q) Console.WriteLine(element); } public static IEnumerable<string> Pair(this IEnumerable<string> source) { string firstHalf = null; foreach (string element in source) if (firstHalf == null) firstHalf = element; else { yield return firstHalf + ", " + element; firstHalf = null; } } } |
Çıktı Ekranı:
- .Net 6.0’da, artık RegEx queryler (ya da herhangi bir custom query), Linq query içine “AsEnumerable()” yazmadan aşağıdaki gibi eklenebilir. Aşağıdaki örnekde, ilk önce tüm mailler büyük harfe çevrilip, “GMAIL” olanları belirlenmiş, ve sonra checkMail RegEx’i ile geçerli olmayanlar filitrelenmiştir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
using System; using System.Linq; using System.Collections.Generic; using System.Text.RegularExpressions; public static class Program { public static void Main() { Regex checkMail = new Regex(@"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$"); var mails = new List<string>() {"bora@gmail.com", "engin@enginpolat.com", "selcuk@gmail", "burak@yahoo.com", "velihotmail.com"}; IEnumerable<string> q = mails.Select(p => p.ToUpper()) .Where(email=>email.Contains("GMAIL")) .OrderBy(n => n) .Where( email => checkMail.Matches(email).Count()>0); foreach (string element in q) Console.WriteLine(element); } } |
Çıktı Ekranı:
4-) “let” Keyword:
- Aşağıda görüldüğü gibi, uzunluğu 3’den büyük isim sorgusu “let” ile “u” değişkenine büyük harfe çevrilerek aktarılmış, daha sonra da “Y” harfi ile bitenler sorgulanmıştır.
1 2 3 4 5 6 7 8 9 10 |
string[] names = {"Tom", "Dick", "Harry", "Mary", "Jay"}; IEnumerable<string> query = from n in names where n.Length > 3 let u = n.ToUpper() where u.EndsWith("Y")select u; foreach (string name in query) { Console.WriteLine(name); } |
Çıktı Ekranı:
- LinqPad 6 ile default gelen DemoDB üzerinden, aşağıdaki query yazılmıştır. LinqPad, aynı Sql Management Studio gibi linq query yazmak için kullanılan bir editördür. “Customer” ve “Order” tabloları, join ile bağlanıp iki tablodan data çekilmiş, daha sonra “let” ile Orderslara ait OrderDetaillardan fiyatı “UnitPrice >60 ” büyük olanlar, Anonymous tipde {ProductName ve UnitPrice} => highValueP’ye “let” ile atanmıştır.
- Son where koşulunda => Ürün fiyatı 60’dan büyük, 2den fazla ürünü olanlar, yeni bir anonymous tip ile {ContactName, Purchas} fiyatı ile geriye dönülmüştür. Bazen ara sorguları bir değişkene atayarak, devam sorguları da bunun üzerinden yapmak, hem kod okunaklığını arttırır hem de oluşan filitre sonucu üzerinden toplu işlem yapmaya izin verir.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from c in Customers join o in Orders on c.CustomerID equals o.CustomerID let highValueP = from od in o.OrderDetails where od.Product.UnitPrice > 60 select new { ProductName = od.Product.ProductName, Price = od.Product.UnitPrice } where highValueP.Count()>=2 select new { c.ContactName, Purchas = highValueP } |
Çıktı Ekranı:
5-)Indexed Filtering:
Aşağıda görüldüğü gibi, satır index’i where koşulunda “i” ile çekilmiştir. Çift olan satırlar ( i % 2 ==0) çekilip, tek satırlar filitrelenmiştir.
1 2 3 4 |
string[] names = {"Bora", "Engin", "Selcuk", "Burak", "Veli"}; IEnumerable<string> query = names.Where((n, i) => i % 2 == 0); foreach (string c in query) Console.WriteLine(c); |
6-) SelectMany:
- Aşağıdaki örnekde, bir dizi elemanı kendisi ile cross match olmaktadır. Aynı Ad ve Soyad, iki kere match olmasın diye, “n != n2” koşulu konulmuştur..
1 2 3 4 5 6 7 8 9 10 |
string[] names = {"Bora", "Judy", "Bill"}; IEnumerable<string> query = from n in names from n2 in names where n != n2 select n + " vs " + n2; foreach (string name in query) { Console.WriteLine(name); } |
Çıktı Ekranı:
- Aşağıdaki örnekde, aslında iç içe 2 döngü bulunmaktadır. Öncelikle tam isim listesi çekilir. Sonra SelectMany => ile Cross Match yapılmaktadır. Yani herbir isim, “fullName.Split()” ile ad, soyad olarak 2 boyutlu bir diziye dönüştürülür, ve herbir eleman yani “ad” ve “soyad” tek tek “tam ad” ile matchlenir. Örn : “{ [Anne , Anne Williams], [Williams, Anne Williams], [John, John Fred], [Fred, Jhon Fred ]}” gibi. Aynı matchlemenin, ad ve soyad için 2 kere tekrarlanmaması amacı ile, “fullName.Split()[0] == name” filitresi konulmuştur. Böylece ilgili eşlemenin, sadece “ad” için yapılması sağlanmıştır. Soyad için bir daha tekrarlanmamıştır.
1 2 3 4 5 6 7 8 9 10 |
string[] fullNames = {"Anne Williams", "John Fred", "Sue Green"}; IEnumerable<string> query = from fullName in fullNames from name in fullName.Split() where fullName.Split()[0] == name select name + " : " + fullName + "'den geliyor"; foreach (string name in query) { Console.WriteLine(name); } |
Çıktı Ekranı:
7-) GroupBy Count :
- Aşağıdaki örnekde, aynı olan hayvan sayısına göre bir gruplama yapılmış ve tekrar eden hayvan sayısına göre, büyükten küçüğe doğru sıralanmıştır. Ekran çıktısı olarak, hayvan adı ve toplam tekrarlama sayısı yazılmıştır.
1 2 3 4 5 6 |
string[] votes = { "Dogs", "Cats", "Cats", "Dogs", "Dogs" }; var query = votes.GroupBy(v=>v=v).OrderByDescending(t=>t.Count()).Select(t2=> new {key = t2.Key, count = t2.Count()}); foreach (var animal in query) { Console.WriteLine($"{animal.key} : {animal.count}"); } |
Çıktı Ekranı:
- Aşağıda görülen örnekde Customer ve Customer’a satılan ürünlerin fiyat toplamı(“Sum(UnitPrice)“), müşteri adına(“ContactName“) göre gruplanıp, fiyatlar büyükten => küçeğe ve müşteri adına göre listelenmektedir.
1 2 3 4 5 6 7 8 9 10 11 |
from c in Customers join o in Orders on c.CustomerID equals o.CustomerID join od in OrderDetails on o.OrderID equals od.OrderID join p in Products on od.ProductID equals p.ProductID group p by new { c.ContactName} into gp orderby gp.Sum(p=>p.UnitPrice) descending, gp.Key.ContactName select new { Customer=gp.Key.ContactName, Price = gp.Sum(p=>p.UnitPrice) } |
LinqPad ile alınan yukarıdaki Linq query’nin sql karşılığı aşağıdaki gibidir :
1 2 3 4 5 6 7 8 |
SELECT [t4].[Contact Name] AS [Customer], [t4].[value2] AS [Price] FROM ( SELECT SUM([t3].[Unit Price]) AS [value], SUM([t3].[Unit Price]) AS [value2], [t0].[Contact Name] FROM [Customers] AS [t0], [Orders] AS [t1], [Order Details] AS [t2], [Products] AS [t3] WHERE ([t2].[Product ID] = [t3].[Product ID]) AND ([t1].[Order ID] = [t2].[Order ID]) AND ([t0].[Customer ID] = [t1].[Customer ID]) GROUP BY [t0].[Contact Name] ) AS [t4] ORDER BY [t4].[value] DESC, [t4].[Contact Name] |
Sonuç Ekranı:
Geldik bir makelenin daha sonuna. Bu makalede Linq query üzerinde, dikkat edilmesi gereken bir kaç gözden kaçabilecek konu üzerinde detaylıca konuştuk. Entity 6.0 ile Linq performansı muazzam bir hale gelmiştir. Özellikle yapılan testler gösteriyor ki, Entity 6.0, 5.0’dan %70 daha hızlı ve %43 daha az memoryde yer kaplamaktadır. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Kaynaklar:
- C# 9.0 in a Nutshell Book
- https://docs.microsoft.com/en-us/ef/ef6/
- https://www.entityframeworktutorial.net/querying-entity-graph-in-entity-framework.aspx
Son Yorumlar