Burak Selim Senyurt(MVP)
Matematik Mühendisi bir .NET Severin Yazıları...

Entity Framework - Many To Many Relations - Link Tablosunu Okumak

Salı, 16 Şubat 2010 09:30 by bsenyurt

Merhaba Arkadaşlar,

Hatırlayacağınız üzere bir önceki yazımızda veritabanı tarafındaki Many-To-Many Relation' ların, Entity Framework tarafında nasıl değerlendirilebileceğine değinmeye çalışmıştık. Dikkat edilmesi gereken önemli noktalardan birisi, veritabanı tarafında yer alan ara bağlantı tablosunun Entity Framework üzerindeki modele alınmayışıydı. Ancak tam bu noktada ortaya bir soru işareti çıkmakta. Ya çalışma zamanında sadece ilişkili primary key alanlarının değerlerini elde etmek istersek. Bir önceki senaryomuza göre bu, hangi Track satırının hangi Playlist' ler ile ve hangi Playlist satırının hangi Track' ler ile ilişkili olduğunun görülmesi anlamına gelmektedir. Kısacası sadece TrackId ve PlaylistId değerlerinin eşleşmesine bakmak istediğimizi düşünebiliriz. Bu durumda aşağıdaki gibi bir kod parçası işimizi görecektir. Şükürler olsun isimsiz tiplere(Anonymous Type)Wink 

using System;
using System.Linq;

namespace ManyToMany
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities entities = new ChinookEntities())
            {
                #region Only Related Columns

                var relations = from t in entities.Tracks
                                from p in entities.Playlists
                                where t.Name.StartsWith("Ab")
                                select new
                                {
                                    t.TrackId,p.PlaylistId
                                };

                foreach (var relation in relations)
                {
                    Console.WriteLine(relation.ToString());
                }

                #endregion
            }
        }
    }
}

Bu LINQ sorgusunda çoklu Select işlemi yapılmaktadır. Sorguya göre Track.Name değeri Ab ile başlayan parçaların TrackId değeleri ile Playlist Entity nesnesi içeriğinde yer alan PlaylistId değerleri arasındaki ilişkiler, yeni bir isimsiz tip(Anonymous Type) içerisine dahil edilmektedir. Kodun çalışması sonucu aşağıdakine benzer bir çalışma zamanı çıktısı elde edilecektir.

Arka planda çalışan SQL Sorgusu ise aşağıdaki gibidir.

SELECT
[Extent1].[TrackId] AS [TrackId],
[Extent2].[PlaylistId] AS [PlaylistId]
FROM  [dbo].[Track] AS [Extent1]
CROSS JOIN [dbo].[Playlist] AS [Extent2]
WHERE [Extent1].[Name] LIKE N'Ab%'

Görüldüğü üzere Anonymous Type kullanımı nedeniyle sadece istediğimiz TrackId ve PlaylistId alanları Select sorgusuna dahil edilmiştir. Buda arka planda çalışan SQL sorgusunda, tüm tablo alanlarının hesaba katılmaması anlamına gelmektedir ki bizim için bir avantajdır ve istediğimizde zaten budur Wink Bir nevi veritabanında yer alan ama Entity olarak kod tarafına aktarılmayan ara tablo içeriğini, LINQ tarafında elde etmiş olduk. Entity Framework tarafında ipucu tadındaki başka konularlar devam ediyor olacağız. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Entity Framework - Many To Many Relations

Cuma, 12 Şubat 2010 14:05 by bsenyurt

Merhaba Arkadaşlar,

Bundan yıllar önce üniversiteden arkadaşım Orkun Şentürk ile birlikte Altunizade Capitol alışveriş merkezinde Dreamcatcher isimli bir bilim kurgu gerilim filmine gittiğimizi hatırlıyorum. (Aslında bilim kurgu filmlerin tam bir hayranıyımdır. Ancak seyrettiklerimin hiç biri Starwars veya Terminator gibilerinin yerini tutmamakta) Film Stephen King' in bir romanından uyarlanmıştı. Sevgili arkadaşım Orkun kitabını okuyarak geldiğinden, film üzerinde daha iyi yorumlar yapmıştı ancak ortak kanımız çok başarılı olmadığı yönündeydi. Açıkçası fazla etkilenmemiştik. Özellikle filmden çıktıktan sonra yaptığımız muhabbet bunu doğrular nitelikteydi. .Net Framework 1.1 ile yazmakta olduğumuz programda kullandığımız SQL 2000 veritabanındaki many-to-many ilişkileri anlamaya çalışıyorduk. O zamanlar bizim için SQL veritabanına bağlanabilmek bile bir lüks iken bu tip alengirli konular korkutucu geliyordu. Filmden daha çok gerildiğimiz bir konuydu.Tongue out Sonuçta bende sevgili Orkun' da evlerimizin yolunu tuttuğumuzda, odamızda bizi bekleyen masa üstü bilgisayarlar ile ne yapacağımızı gayet iyi biliyorduk. Many-To-Many olayını kavramak.

Gerçektende yazılımın ilk yıllarında hepimiz benzer durumları tecrübe etmekteyiz. Pek çok konuyu anlamakta, öğrenmekte güçlük çekiyoruz. Tabi içimizdeki öğrenme arzusunun gücüne bağlı olaraktan, ya çok çalışarak ya da önemli ve dikkat edilmesi gereken noktaları anında yakalayarak ilerleyişimizi sürdürüyoruz. Bir yazılımcı, yıllar sonra geldiği noktadan geriye doğru dönüp baktığındaysa, ilerleyişinin ne kadar hızlı olduğunu net bir şekilde görebilir aslında. Ancak bu yeterli değildir. Dilerseniz sözü fazla uzatmadan bu günkü konumuza geçelim. Bu gün yine Many-To-Many ilişkileri inceliyor olacağız. Ancak bu kez olayı Entity Framework üzerinden değerlendireceğiz. Başlamadan önce veritabanı üzerindeki tablolar arası ilişkilerden(Relations) kısaca bahsetmekte yarar olduğu kanısındayım.

Genel olarak tablolar arasında bire bir(one-to-one), bire çok(one-to-many), çoğa çok (many-to-many) ve self referencing ilişkilerden bahsedilmektedir. Bire-bir ilişkilerde tabloda yer alan bir satırın diğer tabloda yine tek bir satır ile ilişkilendirilmesi söz konusudur. Yaygın olarak kullanılan bire-çok ilişkilerde ise, bir tablodaki satıra diğer bir tablodan n adet satırın bağlanabilmesi mümkündür. Söz gelimi ürün kategorilerinin tutulduğu tablodaki bir satırın, ürünlerin tutulduğu tablodaki n satırı referans etmesi gibi. Self-Referencing ilişkilerde ise bir tablonun herhangibir satırının/satırlarının, kendi içerisindeki bir satırı referans etmesi durumu söz konusudur. Örneğin bir şirketin organizasyon ağacında yer alan bir personelin/personel topluluğunun kime bağlı olduğunun tutulduğu tablolarda, bu tip ilişkiler kullanılabilir.

Gelelim çoğa-çok ilişkilere. Örneğin filmler ve oyunculara ait tablolar olduğunu düşünelim. Burada bir oyuncunun birden fazla filmde rol alması veya bir film içerisinde birden fazla oyuncunun bulunması çok normal ve olasıdır. Bu sebepten her iki tablo birbirleri üzerinde n sayıda satırı referans edebilmektedir. Bu tip bir durumda tablolar arasındaki ilişkiyi ifade etmek için ek bir tablonun kullanılması söz konusudur. Bu tablo üzerinden sağlanan ilişkiler sayesinde çoğa-çok ilişkinin gerçeklenmesi mümkün olabilir. Peki SQL tarafında ek bir tablo yardımıyla ele alınan bu ilişkilerin Entity Framework tarafındaki yansıması nasıldır? Gelin bu durumu basit bir örnek yardımıyla incelemeye çalışalım.

İlk olarak Chinook veritabanında yer alan ve SQL tarafındaki diagramda aşağıdaki şekilde görülen ilişkilere sahip olan tabloları kullanacağımızı belirtelim.

Buradaki senaryoda Playlist ve Track tabloları arasında çoğa-çok ilişki olduğu görülmektedir. Bir başka deyişle bir Playlist birden fazla Track satırına referans edebileceği gibi, bir Track satırı da birden fazla Playlist satırına referans edebilir. Şimdi bu ilişkilerin Entity Framework tarafındaki oluşumuna bir bakalım. (Bu amaçla Visual Studio 2010 Ultimate RC ve Ado.Net Entity Framework 4.0 sürümlerini kullanıyor olacağız. Ancak bu konunun Entity Framework' ün önceki sürümünde de aynen geçerli olduğunu hatırlatalım.) Özellikle 3 tabloyu da seçtiğimizi düşünelim.

Dikkat edileceği üzere Playlist, Track ve PlaylistTrack tablolarının tamamı seçilmiştir. Sihirbaz adımlarını tamamladığımızda ise, aşağıdaki Model diagramının oluştuğunu görürüz.

Hımmm...Wink Dikkat edileceği üzere PlaylistTrack tablosu model diagramına dahil edilmemiştir. Neden?

SQL tarafına bakıldığında PlaylistTrack tablosu üzerinde sadece Playlist ve Track tablolarına ait Primary Key alanları bulunmaktadır. Bunlar dışında ek bir alan yoktur. Bir başka deyişle veritabanı tarafında Playlist ve Track tablolarının many-to-many ilişkilerinin sağlandığı tablodur. Ancak Entity Framework tarafında tabloların sınıflar yardımıyla ve bu sınıfların referans ettikleri diğer sınıfların ise özellikler yardımıyla belirtildiği bilinmektedir. Dolayısıyla Entity Framework tarafında PlaylistTrack isimli bir sınıfın oluşturulmasının bir anlamı yoktur. Dahası olmamasının bir kaybı da yoktur. Bu yüzden Playlist ve Track sınıfları birbirlerine EntityCollection<T> tipinden olan navigasyon özellikleri yardımıyla(Tracks ve Playlists) doğrudan bağlanmışlardır. Bu durum ilişkiyi sağlayan bileşenin(Association nesnesi) özelliklerine bakıldığında da net bir şekilde görülebilir.

Şimdi kod tarafında Many-To-Many ilişkileri nasıl ele alacağımıza bakmaya çalışalım. Örneğin TV-Show listesine ait Track bilgilerini getirmek istediğimizi düşünelim. Bunun için aşağıdaki gibi bir kodlama yapabiliriz.

using System;
using System.Linq;

namespace ManyToMany
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities entities = new ChinookEntities())
            {
                Playlist result = (from pList in entities.Playlists
                                   where pList.PlaylistId == 10
                                   select pList).First();


                Console.WriteLine("{0}-{1}", result.PlaylistId, result.Name);
                foreach (var t in result.Tracks)
                {
                    Console.WriteLine("\t {0}[{1}]", t.Name, t.Milliseconds.ToString());
                }
            }
        }
    }
}

Buna göre aşağıdaki sonuçları elde ederiz.

Bu kod parçasının çalışması sonucunda SQL tarafında, aşağıdaki sorgunun oluşturulduğu gözlemlenecektir.

İlk önce Playlist bilgisinin çekilmesi,

SELECT TOP (1)
[Extent1].[PlaylistId] AS [PlaylistId],
[Extent1].[Name] AS [Name]
FROM [dbo].[Playlist] AS [Extent1]
WHERE 10 = [Extent1].[PlaylistId]

sonrasında ise ilgili Playlist' e bağlı Track' lerin Inner Join sorgusu ile elde edilmesi gerçekleşecektir.

exec sp_executesql N'SELECT
[Extent2].[TrackId] AS [TrackId],
[Extent2].[Name] AS [Name],
[Extent2].[AlbumId] AS [AlbumId],
[Extent2].[MediaTypeId] AS [MediaTypeId],
[Extent2].[GenreId] AS [GenreId],
[Extent2].[Composer] AS [Composer],
[Extent2].[Milliseconds] AS [Milliseconds],
[Extent2].[Bytes] AS [Bytes],
[Extent2].[UnitPrice] AS [UnitPrice]
FROM  [dbo].[PlaylistTrack] AS [Extent1]
INNER JOIN [dbo].[Track] AS [Extent2] ON [Extent1].[TrackId] = [Extent2].[TrackId]
WHERE [Extent1].[PlaylistId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=10

Birde ters tarafan gitmeye çalışalım. Örneğin bir Track' ın geçtiği Playlist' leri bulmak istediğimizi düşünelim. Bu durumda aşağıdaki gibi bir kod parçasını göz önüne alabiliriz.

using System;
using System.Linq;

namespace ManyToMany
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities entities = new ChinookEntities())
            {
                Track track = (from t in entities.Tracks
                               where t.TrackId == 1
                               select t).First();

                Console.WriteLine("{0}-{1}-[{2}]",track.TrackId.ToString(),track.Name,track.Milliseconds.ToString());

                foreach (var p in track.Playlists)
                {
                    Console.WriteLine("\t{0}-{1}",p.PlaylistId.ToString(),p.Name);
                }
            }
        }
    }
}

Bu kod parasına göre, çalışma zamanında TrackId değeri 1 olan parçanın geçtiği Playlist' lerin listesini elde edebildiğimizi görürüz.

Bu kod parçası ise bir öncekine benzer olaraktan aşağıdaki SQL sorgularının çalıştırılmalarına neden olacaktır.

Önce Track bilgileri çekilecek,

SELECT TOP (1)
[Extent1].[TrackId] AS [TrackId],
[Extent1].[Name] AS [Name],
[Extent1].[AlbumId] AS [AlbumId],
[Extent1].[MediaTypeId] AS [MediaTypeId],
[Extent1].[GenreId] AS [GenreId],
[Extent1].[Composer] AS [Composer],
[Extent1].[Milliseconds] AS [Milliseconds],
[Extent1].[Bytes] AS [Bytes],
[Extent1].[UnitPrice] AS [UnitPrice]
FROM [dbo].[Track] AS [Extent1]
WHERE 1 = [Extent1].[TrackId]

sonrasında ise ilgili Track satırının geçtiği Playlist satırlarının bulunması için gerekli Inner Join sorgusu çalıştırılacaktır.

exec sp_executesql N'SELECT
[Extent2].[PlaylistId] AS [PlaylistId],
[Extent2].[Name] AS [Name]
FROM  [dbo].[PlaylistTrack] AS [Extent1]
INNER JOIN [dbo].[Playlist] AS [Extent2] ON [Extent1].[PlaylistId] = [Extent2].[PlaylistId]
WHERE [Extent1].[TrackId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1

Pek tabi olarak Many-to-Many ilişki söz konusu olan tablolar üzerinden Update, Delete veya Insert işlemleri de yapılabilir. Burada, özellikle Delete operasyonlarında tarafların nasıl tepki göstereceği önemlidir. Normal şartlarda Association nesnesi üzerinde yer alan End1 OnDelete ve End2 OnDelete isimli özelliklerin değerleri none olarak belirlenmiştir. Ancak istenirse bunlar Cascade değerine çekilebilir. Pek tabi ilişkinin nasıl bir tepki vereceğine bağlı olaraktan SQL tarafından Foreign Key' ler ile alakalı istisnaların alınması da muhtemeldir. Şimdi bu durumu incelemeye çalışalım.

using System;
using System.Linq;

namespace ManyToMany
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities entities = new ChinookEntities())
            {
                #region Delete and Foreign Key Violation

                var trackOnDelete = (from t in entities.Tracks
                                     where t.TrackId == 3502
                                     select t).First();

                entities.DeleteObject(trackOnDelete);

                entities.SaveChanges();

                #endregion
            }
        }
    }
}

Bu kod parçasına göre TrackId değeri 3502 olan Track satırı silinmeye çalışılmaktadır. Ancak Track' lar çok doğal olarak Playlist' lere bağlıdır. Bu nedenle silme işlemi sırasında aşağıdaki çalışma zamanı istisnası(Runtime Exception) alınacaktır.

Burada sebep, söz konusu alan için bir Relation' ın var olmasıdır. Dolayısıyla öncelikle silinmek istenen Track satırı ile bağlı olduğu Playlist satırları arasındaki ilişkileri kaldırmak gerekmektedir. Silme operasyonunun gerçeklenmesi için, silinmek istenen Track' ın dahil olduğu Playlist nesnelerini bulup, herbirinin Tracks özelliği üzerinden Remove metodunun çalıştırılması yeterlidir. Mi acaba? Undecided 

Bu tip bir kod yazılmak istendiğinde elde edilen Playlist.Tracks özellikleri üzerinden yapılan silme hareketleri, koleksiyonunun değişmesine neden olacağından, çalışma zamanında Concurrency hatası alınacaktır. Çok şükür ki artık elimizin altında .Net Framework 4.0 içerisine gömülü olarak gelen Concurrent koleksiyonlar bulunmaktadır.(Concurrent Collections (Eş Zamanlı Koleksiyonlar) [Beta 1] , Concurrent Collections : Macera BlockingCollection ile Devam Ediyor [Beta 1] ) Buna göre kodu aşağıdaki gibi geliştirebiliriz.

using System;
using System.Linq;
using System.Collections.Concurrent;

namespace ManyToMany
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities entities = new ChinookEntities())
            {
                #region Delete and Foreign Key Violation

                var trackOnDelete = (from t in entities.Tracks
                                     where t.TrackId == 3503
                                     select t).First();

                ConcurrentBag<Playlist> playList = new ConcurrentBag<Playlist>(trackOnDelete.Playlists);
                foreach (var pl in playList)
                {
                    pl.Tracks.Remove(trackOnDelete);
                }

                entities.DeleteObject(trackOnDelete);

                entities.SaveChanges();

                #endregion
            }
        }
    }
}

Biraz performans kaybı söz konusu olabilir ancak silme işlemi başarılı bir şekilde gerçekleştirilebilecektir. Özellikle SQL tarafında çalıştırılan sorgulara bakıldığında, aşağıdaki ifadelerin icra edildiği gözlemlenir.

Önce Track ve Playlist tabloları arasındaki çoğa-çok ilişkiyi sağlayan PlaylistTrack tablosundaki ilgili satırlar silinir.

exec sp_executesql N'delete [dbo].[PlaylistTrack]
where (([PlaylistId] = @0) and ([TrackId] = @1))',N'@0 int,@1 int',@0=1,@1=3503

exec sp_executesql N'delete [dbo].[PlaylistTrack]
where (([PlaylistId] = @0) and ([TrackId] = @1))',N'@0 int,@1 int',@0=5,@1=3503

exec sp_executesql N'delete [dbo].[PlaylistTrack]
where (([PlaylistId] = @0) and ([TrackId] = @1))',N'@0 int,@1 int',@0=8,@1=3503

exec sp_executesql N'delete [dbo].[PlaylistTrack]
where (([PlaylistId] = @0) and ([TrackId] = @1))',N'@0 int,@1 int',@0=12,@1=3503

exec sp_executesql N'delete [dbo].[PlaylistTrack]
where (([PlaylistId] = @0) and ([TrackId] = @1))',N'@0 int,@1 int',@0=13,@1=3503

Artık sorun yoktur, nitekim Track tablosu ile Playlist arasındaki ilişkiler ortadan kalkmıştır. Buna göre son olarak, Track tablosundan ilgili satırın silinmesi işlemi gerçekleştirilir.

exec sp_executesql N'delete [dbo].[Track]
where ([TrackId] = @0)',N'@0 int',@0=3503

Insert ve Update işlemlerinin incelenmesini de siz değerli okurlarıma bırakıyorum. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

ManyToMany_RC.rar (47,16 kb) [Örnek Visual Studio 2010 Ultimate RC sürümü üzerinde geliştirilmiş ve test edilmiştir]

Eski Dost ve Entity Framework 4.0

Salı, 9 Şubat 2010 18:28 by bsenyurt

Merhaba Arkadaşlar,

Bildiğiniz üzere .Net Framework 4.0 ve Visual Studio 2010 sürümlerinin çıkmasına az bir zaman kaldı(Hatta yarın RC sürümü herkese açık olacak). Blog yazısını hazırladığım zaman itibariyle Microsoft' taki güvenilir kaynaklar ve MSDN yazarlarından edinilen bilgilere göre, sürümün Nisan 2010 içerisinde çıkması bekleniyor. Tabi yeni bir sürüm denilince eski sürüm ile aradaki farklılıkları bilmek, nereden nereye gelindiği ve nelerin düzeltildiğini öğrenmek, beklentilerin karşılanıp karşılanmadığına bakmak oldukça önemli. Bende buna istinaden yazımızın ilerleyen kısımlarında, Ado.Net Entity Framework 4.0 ile bir önceki versiyonu arasında, sorgu teknikleri açısından oluşan farkları aktarmaya çalışıyor olacağım. Ado.Net takımının 4.0 sürümündeki hedeflerinden biriside SQL sorgularının daha anlaşılır olmasını sağlamak. Buna ek olarak gereksiz SQL ifadelerinin kullanılmasından uzaklaşılmaya çalışılmış ve hatta önceki Entity Framework sürümünde SQL sorgusuna dönüştürülemeyen LINQ ifadeleri kullanılabilir hale getirilmiş. Tabi daha da fazla fark bulunabilir. Ancak MSDN ve Ado.Net takımının gerek blog yazılarından gerek Webcast' lerinden takip ettiğim kadarı ile aşağıda yer alan 4 önemli farklılık göze çarpmaktadır. Çok doğal olarak söz konusu iyileştirmeler LINQ ifadelerinin SQL sorgularına dönüştürülmesinde devreye giren motor üzerinde yer almaktadır.

Not : Örneklerimizde yer alan LINQ sorguları .Net Framework 3.5 odaklı olan Visual Studio 2008 ve .Net Framework 4.0 odaklı olan Visual Studio 2010 Ultimate Beta 2 ürünleri üzerinde yazılmıştır. Özellikle Ado.Net Entity Framework 4.0 üzerinden daha nihai bir sürüme ulaşılmadığı için ilereyen zamanlarda değişiklikler gözlemlenebilir. LINQ sorgularının icrası sırasında, SQL Server Profiler aracı kullanılarak arka planda yürütülen SQL sorgularının elde edilmesi sağlanmıştır. Ayrıca örnekte Codeplex üzerinden sunulan Chinook veritabanı kullanılmıştır.

 Vaka 1 : Sorgularda In Desteği

Açıklama

Ado.Net Entity Framework 4.0 öncesindeki sürümde SQL tarafında In anahtar kelimesine dönüşebilecek sorgu desteği bulunmamaktadır. 4.0 versiyonunda In' e dönüştürülebilme desteği belirli ölçüde bulunmaktadır. Aşağıdaki LINQ sorgusuna göre, Berlin ve Paris şehirlerinde yer alan müşteriler elde edilmeye çalışılmaktadır. 4.0 öncesi sürümde bu tip bir LINQ sorgusunda yer alan Contains metodunun SQL tarafına dönüştürülemediği görülür. Ancak 4.0 versiyonunda SQL tarafına In olarak aktarılması sağlanmaktadır.

Önceki Versiyon - LINQ Sorgusu

                string[] cityNames = { "Berlin", "Paris" };

                var result = from customer in entites.Customer
                             where cityNames.Contains(customer.City)
                             select customer;

                foreach (var r in result)
                {
                    Console.WriteLine("{0} {1} {2}", r.Email, r.FirstName, r.LastName);
                }

Önceki Versiyon - SQL Sorgusu

Bir SQL sorgusu yürütülememektedir nitekim çalışma zamanında aşağıdaki Exception mesajı alınacaktır.

4.0 - LINQ Sorgusu

                string[] cityNames = { "Berlin", "Paris" };

                var result = from customer in entites.Customers
                             where cityNames.Contains(customer.City)
                             select customer;

                foreach (var r in result)
                {
                    Console.WriteLine("{0} {1} {2}", r.Email, r.FirstName, r.LastName);
                }

4.0 - SQL Sorgusu

SELECT
[Extent1].[CustomerId] AS [CustomerId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName],
[Extent1].[Company] AS [Company],
[Extent1].[Address] AS [Address],
[Extent1].[City] AS [City],
[Extent1].[State] AS [State],
[Extent1].[Country] AS [Country],
[Extent1].[PostalCode] AS [PostalCode],
[Extent1].[Phone] AS [Phone],
[Extent1].[Fax] AS [Fax],
[Extent1].[Email] AS [Email],
[Extent1].[SupportRepId] AS [SupportRepId]
FROM [dbo].[Customer] AS [Extent1]
WHERE [Extent1].[City] IN (N'Berlin',N'Paris')

Vaka 2 - Gruplamada Cast Sorunu

Açıklama

Gruplama uygulanmış olan bir LINQ sorgusunda Count genişletme metodunun(Extension Method) kullanılması halinde bir önceki versiyonda Cast operatörünün SQL sorgusuna dahil edildiği görülür. Ancak 4.0 versiyonunda bu gereksiz durum düzeltilmiştir.

Önceki Versiyon - LINQ Sorgusu

                var result = from track in entites.Track
                             group track by track.Composer into trackGrp
                             select new
                             {
                                 trackGrp.Key,
                                 Count = trackGrp.Count(),
                                 Sum = trackGrp.Sum<Track>(t => t.UnitPrice),
                                 Max = trackGrp.Max<Track>(t => t.UnitPrice),
                                 Min = trackGrp.Min<Track>(t => t.UnitPrice)
                             };

                foreach (var r in result)
                {
                    Console.WriteLine(r.ToString());
                }

Önceki Versiyon - SQL Sorgusu

SELECT
1 AS [C1],
[GroupBy1].[K1] AS [Composer],
[GroupBy1].[A1] AS [C2],
[GroupBy1].[A2] AS [C3],
[GroupBy1].[A3] AS [C4],
[GroupBy1].[A4] AS [C5]
FROM ( SELECT
 [Extent1].[Composer] AS [K1],
 COUNT( CAST( 1 AS bit)) AS [A1],
 SUM([Extent1].[UnitPrice]) AS [A2],
 MAX([Extent1].[UnitPrice]) AS [A3],
 MIN([Extent1].[UnitPrice]) AS [A4]
 FROM [dbo].[Track] AS [Extent1]
 GROUP BY [Extent1].[Composer]
)  AS [GroupBy1]

4.0 - LINQ Sorgusu

                var result = from track in entites.Tracks
                             group track by track.Composer into trackGrp
                             select new
                             {
                                 trackGrp.Key,
                                 Count = trackGrp.Count(),
                                 Sum = trackGrp.Sum<Track>(t => t.UnitPrice),
                                 Max = trackGrp.Max<Track>(t => t.UnitPrice),
                                 Min = trackGrp.Min<Track>(t => t.UnitPrice)
                             };

                foreach (var r in result)
                {
                    Console.WriteLine(r.ToString());
                }

4.0 - SQL Sorgusu

SELECT
1 AS [C1],
[GroupBy1].[K1] AS [Composer],
[GroupBy1].[A1] AS [C2],
[GroupBy1].[A2] AS [C3],
[GroupBy1].[A3] AS [C4],
[GroupBy1].[A4] AS [C5]
FROM ( SELECT
 [Extent1].[Composer] AS [K1],
 COUNT(1) AS [A1],
 SUM([Extent1].[UnitPrice]) AS [A2],
 MAX([Extent1].[UnitPrice]) AS [A3],
 MIN([Extent1].[UnitPrice]) AS [A4]
 FROM [dbo].[Track] AS [Extent1]
 GROUP BY [Extent1].[Composer]
)  AS [GroupBy1]

Vaka 3 - Join sorgularında Is Null Kontrolü

Açıklama

LINQ tarafında icra edilen aşağıdaki gibi bir Join sorgusunda, bir önceki versiyonda üretilen SQL ifadesinde anahtar alan için IS NULL kontrolü yapıldığı görülmektedir. 4.0 sürümünde ise gereksiz olan bu kontrol SQL tarafına aktarılmamaktadır.

Önceki Versiyon - LINQ Sorgusu

                var result = from artist in entites.Artist
                             join album in entites.Album
                             on artist.ArtistId equals album.Artist.ArtistId
                             select new
                             {
                                 ArtistName = artist.Name,
                                 AlbumTitle = album.Title
                             };

                foreach (var r in result)
                {
                    Console.WriteLine(r.ToString());
                }

Önceki Versiyon - SQL Sorgusu

SELECT
1 AS [C1],
[Extent1].[Name] AS [Name],
[Extent2].[Title] AS [Title]
FROM  [dbo].[Artist] AS [Extent1]
INNER JOIN [dbo].[Album] AS [Extent2] ON ([Extent1].[ArtistId] = [Extent2].[ArtistId]) OR (([Extent1].[ArtistId] IS NULL) AND ([Extent2].[ArtistId] IS NULL))

4.0 - LINQ Sorgusu

                var result = from artist in entites.Artists
                             join album in entites.Albums
                             //on artist.ArtistId equals album.Artist.ArtistId
                             on artist.ArtistId equals album.ArtistId //(Zaten yeni sürümde yandaki gibi yazabiliyoruz artık)
                             select new
                             {
                                 ArtistName = artist.Name,
                                 AlbumTitle = album.Title
                             };

                foreach (var r in result)
                {
                    Console.WriteLine(r.ToString());
                }

4.0 - SQL Sorgusu

SELECT
[Extent1].[ArtistId] AS [ArtistId],
[Extent1].[Name] AS [Name],
[Extent2].[Title] AS [Title]
FROM  [dbo].[Artist] AS [Extent1]
INNER JOIN [dbo].[Album] AS [Extent2] ON [Extent1].[ArtistId] = [Extent2].[ArtistId]

Vaka 4 - Skip, Take gibi Metod Kullanımlarında Tüm Alanların Sorguya Dahil Edilmesi

Açıklama

Aşağıdaki LINQ sorgusuna göre artistlerin tersten sıralanan adlar listesi ilk 10 satır atlanarak elde edilmektedir. 4.0 öncesi versiyonda bu tip bir LINQ sorgusunun çalıştırılması halinde SQL tarafında oluşturulan Sub SELECT sorgusunda aslında LINQ sorgusuna dahil edilmeyen alanlarında hesaba katıldığı gözlemlenir. 4.0 versiyonunda ise gereksiz alanların SELECT sorgusuna alınmasının önüne geçilmiştir.

Önceki Versiyon - LINQ Sorgusu

                var result = (from artist in entites.Artist
                              orderby artist.Name descending
                              select artist.Name).Skip(10);

                foreach (var r in result)
                {
                    Console.WriteLine(r);
                }

Önceki Versiyon - SQL Sorgusu

SELECT
[Extent1].[Name] AS [Name]
FROM ( SELECT [Extent1].[ArtistId] AS [ArtistId], [Extent1].[Name] AS [Name], row_number() OVER (ORDER BY [Extent1].[Name] DESC) AS [row_number]
 FROM [dbo].[Artist] AS [Extent1]
)  AS [Extent1]
WHERE [Extent1].[row_number] > 10
ORDER BY [Extent1].[Name] DESC

4.0 - LINQ Sorgusu

                var result = (from artist in entites.Artists
                              orderby artist.Name descending
                              select artist.Name).Skip(10);

                foreach (var r in result)
                {
                    Console.WriteLine(r);
                }

4.0 - SQL Sorgusu

SELECT
[Extent1].[Name] AS [Name]
FROM ( SELECT [Extent1].[Name] AS [Name], row_number() OVER (ORDER BY [Extent1].[Name] DESC) AS [row_number]
 FROM [dbo].[Artist] AS [Extent1]
)  AS [Extent1]
WHERE [Extent1].[row_number] > 10
ORDER BY [Extent1].[Name] DESC

Görüldüğü üzere Ado.Net Entity Framework 4.0 sürümünde özellikle SQL sorgularına dönüştürme işlemlerinde, motor üzerinde bazı yenilemelerin yapıldığı ve gereksiz işlemlerin önüne geçilmeye çalışıldığı gözlemlenebilmektedir. Açıkçası bu konu ile ilişkili olaraktan çıkacak kitapları merakla bekliyorum. Özellikle Julia Lerman' ın daha önceki Programming Entity Framework kitabını okuyanlar eminim 4.0 için olan versiyonuda sabırsızlıkla ve merakla bekliyordur. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Differences.rar (183,69 kb) [Örnek Visual Studio 2010 Ultimate Beta 2 üzerinde geliştirilmiştir. Yarın public olarak yayınlanacak RC sürümü için test edilmemiştir]

Ado.Net Entity Framework 4.0 - Lazy Loading [Beta 2]

Pazartesi, 7 Aralık 2009 11:27 by bsenyurt

Merhaba Arkadaşlar,

Ado.Net Entity Framework' ün en çok eleştirilen yönlerinden birisi, ORM(Object Relational Mapping) ihtiyacını karşılayacak bir alt yapı olarak tasarlanmasına rağmen ORM' in karakteristik özelliklerinden birisi olan Lazy Loading' in(Bir nesnenin ihtiyaç duyulduğu noktada veri kaynağından yüklenmesini-Load hedefleyen bir tasarım kalıbı olarak düşünülebilir) tam manada desteklemiyor olmasıdır. Zaman içerisinde Deferred Loading veya Explicity Lazy Loading gibi isimler ile Ado.Net Entity Framework içerisine bir takım özellikler eklenerek bu eksiklik ortadan kaldırılmaya çalışılsada gerçek manada 4.0 versiyonunun Beta 2 sürümünde, beklenildiği gibi bir iyileştirme olduğunu görmekteyiz. Bu yazımızdaki temel amacımız ise, Ado.Net Entity Framework' ün bir önceki sürümünde durumun ne olduğunu anlamaya çalışıp, 4.0 Beta 2 sürümünde Lazy Loading adına hangi özelliğin getirildiğini(belkide getirilmediğini) ve nasıl kullanıldığını görebilmektir. Örneklerimizde Codeplex üzerinden açık kaynak olarak yayınlanan Chinook veritabanını kullanıyor olacağız. Haydi parmakları sıvayalım Wink Örneklerimizden ilkini Visual Studio 2008 diğerini ise Visual Studio 2010 Ultimate Beta 2 ortamında geliştiriyor olacağız. Ancak her iki örnekte aşağıdaki şekilde görülen tabloları kullanıyor olacak.

Visual Studio 2008 ve Ado.Net Entity Framework V1.0

İlk olarak Console uygulamamızda aşağıdaki gibi bir kod parçası geliştirdiğimizi düşüelim.

using System;
using System.Linq;

namespace Before
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities context = new ChinookEntities())
            {
                var albums = from a in context.Album
                             select a;

                foreach (Album albm in albums)
                    Console.WriteLine("{0} [{1}]",albm.Title,albm.Track.Count.ToString());

            }
        }
    }
}

Bu kod parçasında Album listesi elde edildikten sonra Title bilgileri ile birlikte, o anki albüme ait Track sayıları ekrana yazdırılmaktadır. Yani albümler ve bu albümler altında kaç şarkı olduğu bilgisine ulaşmak istediğimizi düşünebilir. Buna göre çalışma zamanı görüntüsü aşağıdaki gibidir.

Hatta SQL Server Profiler aracına bakıldığında söz konusu kod parçası için aşağıdaki sorgunun çalıştırıldığı görülür.

SELECT
[Extent1].[AlbumId] AS [AlbumId],
[Extent1].[Title] AS [Title],
[Extent1].[ArtistId] AS [ArtistId]
FROM [dbo].[Album] AS [Extent1]

Bir sorun var mı? Undecided Aslında var. Dikkat edileceği üzere güncel Album Entity nesnesi ile ilişkili olan Track nesneslerinin toplam sayıları(Count) her zaman 0 olarak elde edilmiştir. Bir başka deyişle X Entity nesnesi ile ilişkili olan Y Entity nesnesine dair herhangibir sorgunun işletilmesi söz konusu olmamıştır. Oysaki Lazy Loading kalıbına göre çalışma zamanındaki Album nesne örneklerine ait Track özellikleri üzerinden o anki Track nesne örneğinin herhangibir üyesine erişilmek istendiğinde(Örnekteki Count gibi), SQL tarafındada gerekli sorguların çalıştırılacağı düşünülür. Peki ilk versiyonda yaşanan bu durum üzerine ne yapılmaktaydı? Temel olarak iki basit yöntem ile bu durumu çözüme kavuşturabiliriz. Bunlardan birisi Include metodunun LINQ ifadesinde aşağıdaki gibi kullanılmasıdır.

var albums = from a in context.Album.Include("Track")
                             select a;

Bu durumda çalışma zamanı görüntüsü aşağıdaki gibi olacaktır.

Görüldüğü üzere albümler içerisinde yer alan parçaların toplam sayıları elde edilebilmiştir. Bunun için SQL tarafında da aşağıdaki sorgunun çalıştırıldığı gözlemlenmektedir.

SELECT
[Project1].[AlbumId] AS [AlbumId],
[Project1].[Title] AS [Title],
[Project1].[ArtistId] AS [ArtistId],
[Project1].[C1] AS [C1],
[Project1].[C3] AS [C2],
[Project1].[C2] AS [C3],
[Project1].[TrackId] AS [TrackId],
[Project1].[Name] AS [Name],
[Project1].[MediaTypeId] AS [MediaTypeId],
[Project1].[GenreId] AS [GenreId],
[Project1].[Composer] AS [Composer],
[Project1].[Milliseconds] AS [Milliseconds],
[Project1].[Bytes] AS [Bytes],
[Project1].[UnitPrice] AS [UnitPrice],
[Project1].[AlbumId1] AS [AlbumId1]
FROM ( SELECT
 [Extent1].[AlbumId] AS [AlbumId],
 [Extent1].[Title] AS [Title],
 [Extent1].[ArtistId] AS [ArtistId],
 1 AS [C1],
 [Extent2].[TrackId] AS [TrackId],
 [Extent2].[Name] AS [Name],
 [Extent2].[AlbumId] AS [AlbumId1],
 [Extent2].[MediaTypeId] AS [MediaTypeId],
 [Extent2].[GenreId] AS [GenreId],
 [Extent2].[Composer] AS [Composer],
 [Extent2].[Milliseconds] AS [Milliseconds],
 [Extent2].[Bytes] AS [Bytes],
 [Extent2].[UnitPrice] AS [UnitPrice],
 CASE WHEN ([Extent2].[TrackId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2],
 CASE WHEN ([Extent2].[TrackId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C3]
 FROM  [dbo].[Album] AS [Extent1]
 LEFT OUTER JOIN [dbo].[Track] AS [Extent2] ON [Extent1].[AlbumId] = [Extent2].[AlbumId]
)  AS [Project1]
ORDER BY [Project1].[AlbumId] ASC, [Project1].[C3] ASC

Dikkat edileceği üzere Album ve Track tablolarının Left Outer Join ile birleştirilmesi söz konusudur. Dahası, sonucun elde edilmesi için tek bir SQL ifadesinin çalıştırılması yeterli olmuştur. Ancak dikkat çeken noktalardan biriside sadece Count değeri ile ilgilenmemize rağmen Track tablosundaki tüm alanların ifadeye dahil edilmesidir. Zaten Count hesabı için SQL tarafında çalıştırılmış bir ifade de bulunmamaktadır. Bunun yerine kod tarafına çekilen Track listesinin toplam değerinin hesaplanması söz konusudur.

Diğer bir teknik ise Load metodunun kullanılmasıdır. Bu amaçla foreach döngüsünde aşağıdaki düzenlemeyi yaptığımızı düşünelim.

foreach (Album albm in albums)
{
   albm.Track.Load();
   Console.WriteLine("{0} [{1}]", albm.Title, albm.Track.Count.ToString());
}

Bu durumda çalışma zamanı görüntüsü Include kullanımındaki ile aynı olacaktır.

SQL Server Profiler aracına bakıldığında ise aşağıdakine benzer bir ekran görüntüsü ile karşılaşılacaktır.

Dikkat edileceği üzere foreach döngüsü içerisinde Load metodunun çağırıldığı her an arka planda bir SQL sorgusunun çalıştırıldığı, yine sadece Count ile ilgilenmemize rağmen tüm Track alanlarının işe katıldığı gözlemlenebilir. Hatta Count ile ilişkili olarak SQL tarafında çalıştırılan bir ifade bulunmamaktadır. Bu değer, kod tarafındaki Track listesinin ilgili Album için elde edilmesinden sonra hesaplanmaktadır. Aslında tam bu noktada, ele alınan senaryo için Include metodunun kullanımının, Load metoduna göre daha efektif olduğunu düşünebiliriz. Nitekim istemci ve SQL sunucusu arasında sadece tek bir sorgusunun çalıştırılması söz konusudur. Diğer yandan foreach döngüsü içerisinde senaryoya göre bazı koşullar sağlandığı takdirde Count özelliğinin kullanılması istendiği durumlarda, Load metodunun tercih edilmesi yoluna gidilebilir. Her neyse...Bakalım Ado.Net Entity Framework 4.0 Beta 2 içerisinde gerçek anlamda Lazy Loading için ne yapılmıştır.

Visual Studio 2010 Beta 2 ve Ado.Net Entity Framework 4.0 Beta 2

Yeni sürümde ObjectContext türevli tip üzerinden uygulanabilen LazyLoadingEnabled isimli bir özellik(Property) bulunmaktadır. Aslında varsayılan olarak Lazy Laoding özelliğinin açık olduğunu söyleyebiliriz. Aynı örneği Visual Studio 2010 Beta 2 üzerinde denediğimizi düşünelim.(Entity adlarının oluşturulması sırasında çoğullaştırma özelliği etkinleştirilmiştir. Bu nedenle Albums ve Tracks isimlendirmeleri söz konusudur)

using System;
using System.Linq;

namespace ReallyLazy
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChinookEntities context = new ChinookEntities())
            {
                var albumsWithTracks = from a in context.Albums
                                       select a;

                foreach (Album albm in albumsWithTracks)
                {
                    Console.WriteLine("{0} ({1})",albm.Title,albm.Tracks.Count.ToString());
                }
            }
        }
    }
}

Çalışma zamanında aşağıdaki sonuçları elde ederiz.

Dikkat edileceği üzere kod içerisinde herhangibir şey belirtmememize rağmen Album nesneleri ile ilişkili olan Track nesnelerinin Count özelliklerinin değerleri elde edilebilmiştir. Peki arka planda çalışan SQL sorgusu(sorguları)? Wink İşte SQL Server Profiler aracından elde edilen sonuçlar;

Görüldüğü gibi foreach döngüsü içerisinde Track.Count özelliğine gidildiği her noktada bir SQL sorgusu çalıştırılmış ve o anki albüme bağlı olan parça bilgileri çekilmiştir. Dikkat edileceği üzere Count için SQL tarafında yapılmış özel bir sorgulama yoktur. Bu değer kod tarafında elde edilmektedir. Aslında bu çalışma şeklinin Load metodunun kullanımındaki ile benzer olduğunu söyleyebiliriz. Benzer diyorum çünkü Load kullanımında çalıştırılan SQL sorgusu ile buradaki arasında fark vardır. (Bakalım iki şekil arasındaki 9 farkı bulabilecek misiniz? Smile )

Kişisel Not : Aslında bazı blog yazılarında LazyLoadingEnabled özelliğinin true olması halinde bu şekilde çalıştığı gösterilmiştir. İşte sürüm farklılıklarının bir sonucu daha. Bu nedenle Beta 2 üzerinde yazdığımız bu konunun gelecek sürümlerde değişikliğe uğraması söz konusu olabilir.

Şimdi kodumuzda aşağıdaki değişikliği yaptığımızı düşünelim.

using (ChinookEntities context = new ChinookEntities())
{
   context.ContextOptions.LazyLoadingEnabled = false;

Burada LazyLoadingEnabled özelliğine false değer atanması sonucu aşağıdaki sonuçlar ile karşılaşılacaktır.

Beklediğimiz gibi Lazy Loading özelliğini kapattık. Peki ya SQL tarafı? İşte SQL Server Profiler' dan yakalanan SQL sorgusu.

SELECT
[Extent1].[AlbumId] AS [AlbumId],
[Extent1].[Title] AS [Title],
[Extent1].[ArtistId] AS [ArtistId]
FROM [dbo].[Album] AS [Extent1]

Sanırım yazımızın başladığı noktadaki sonuçlara döndük ne dersiniz? Wink Özetle Ado.Net Entity Framework 4.0 Beta 2 sürümünde Lazy Loading kabiliyetinin varsayılan olarak açık geldiğini ve istendiğinde Context nesnesinin LazyLoadingEnabled özelliğine atanacak false değeri ile kapatılabileceğini görmüş olduk. Bakalım sürümün ileriki versiyonlarında ne gibi yenilikler olacak. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Before.rar (41,07 kb)

ReallyLazy.rar (98,20 kb)

Ado.Net Entity Framework 4.0 - Stored Procedures ve Complex Types

Pazartesi, 16 Kasım 2009 10:55 by bsenyurt

Merhaba Arkadaşlar,

Ado.Net Entity Framework 4.0 ile birlikte gelecek/gelmekte olan yeniliklerden birisi de, Stored Procedure' lerin dönüş tipi ile alakalıdır. Henüz tam olarak bitirilememiş olan bu özellik şu anki haliyle bir Stored Procedure' den geriye karmaşık bir tipinin(Complex Type) döndürülebilmesine izin vermektedir. Bunun için Designer tarafında destek sunulmaktadır. Aslında önceki Ado.Net Entity Framework sürümünde bir Stored Procedure' ün Entity modeli içerisine eklenmesi sonrasında dönüş kümesinin Scalars veya Entities olarak kullanılması sağlanabilmekteydi. Ancak bir Stored Procedure çıktısının Complex Type bazlı olaraktan kod tarafında ele alınamayışı da de önemli bir eksiklikti. Bakalım 4.0 versiyonunda bu eksikliği gidermek adına neler yapılmış. Yazımızın ilerleyen kısımlarında bu özeliği anlamaya çalışıyor olacağız.

İlk olarak kendimize kobay bir Stored Procedure seçelim WinkBu amaçla Adventure Works veritabanında yer alan uspGetManagerEmployees Stored Procedure' ünü kullanabiliriz. Bu procedure parametre olarak ManagerID isimli int tipinden bir dğer almakta ve aşağıdaki örnek çıktıyı üretmektedir.

Pekala Stored Procedure' ümüzün çıktısı, RecursionLevel,ManagerID,ManagerFirstName, ManagerLastName, EmployeeID, FirstName ve LastName alanlarının karşılıklarını özellik(Property) olarak içeren sınıfa ait nesne örneklerinden oluşan bir nesne kümesi ile ifade edilebilir. Bunu gerçekleştirmek için Visual Studio 2010 Ultimate Beta 2 sürümünde oluşturacağımız Console uygulamamıza Adventure Works veritabanı için yeni bir Ado.Net Entity Data Model öğesi ekleyip sadece uspGetManagerEmployees SP' sini seçtiğimizi düşünelim.

Şimdi adım adım ilerleyerek Complex Type üretimini gerçekleştireceğiz.

  1. İlk olarak Model Browser ve AdventureWorksModel.Store kısmından ilgili Stored Procedure adına sağ tıklanıp Add Function Import seçimi yapılmalıdır.
  2. Seçimin ardından gelen ekranda Get Column Information düğmesine basıldığı takdirde ilgili Stored Procedure' den dönen sonuç kümesi için uygun olan kolon bilgilerinin getirildiği görülür. Burada hem EDM Type hemde SQL Type bilgileri yer almaktadır.
  3. Sonraki adımda ise Create New Complex Type düğmesine tıklanarak Stored Procedure' ün sonuç kümesi için gerekli sınıfın üretilmesi sağlanır.
  4. İstenirse üretilen tip adı Complex kutucuğundan değiştirilir(Biz örneğimizde tip adını ManagerEmployees olarak yeniledik)
  5. Ok tuşuna basılarak işlem tamamlanır.

Aşağıdaki şekilde bahsetmiş olduğumuz adımlar görsel olarak özetlenmektedir.

Şimdi arka planda neler olduğuna bir bakalım. İlk olarak Entity Data Model tarafında söz konusu Stored Procedure için bir Return Type ve Mapping Details kısmında gerekli kolon-özellik eşleştirmelerinin yapıldığı görülür.

Buna ek olarak Return Type için kod tarafında ManagerEmployees isimli ComplexObject türevli bir sınıfın üretildiği gözlemlenir. Class Diagram görüntüsünde bu durum açık bir şekilde izlenebilmektedir.

Çok doğal olarak söz konusu Stored Procedure' ün kod içerisinde kullanılabilmesi için gerekli özelliğin de AdventureWorksEntities Context tipi içerisine eklendiği görülebilir.

Dikkat edileceği üzere uspGetManagerEmployees isimli özelliğin(Property) dönüşü ObjectResult<ManagerEmployees> tipindendir. Bu tip IEnumerable<T> ve IEnumerable arayüzlerini(Interface) implemente ettiğinden LINQ sorgularında kaynak veri olarak kullanılabilir.

Dilerseniz birde söz konusu Stored Procedure' e ait çıktıyı örnek kod parçasında değerlendirmeye çalışalım. Bunun için aşağıdaki gibi basit bir kodlama yapmamız yeterli olacaktır.

using System;
using System.Data.Objects;
using System.Linq;

namespace SPAndComplexType
{
    class Program
    {
        static void Main(string[] args)
        {
            using (AdventureWorksEntities context = new AdventureWorksEntities())
            {
                ObjectResult<ManagerEmployees> resultSet=context.uspGetManagerEmployees(16);

                var subResult = from me in resultSet
                                where me.FirstName[0] == 'M'
                                select me;

                foreach (var managerEmployee in subResult)
                {
                    Console.WriteLine("{0} {1} {2} {3} {4} {5} {6}",
                        managerEmployee.EmployeeID.ToString(),
                        managerEmployee.FirstName,
                        managerEmployee.LastName,
                        managerEmployee.ManagerFirstName,
                        managerEmployee.ManagerID,
                        managerEmployee.ManagerLastName,
                        managerEmployee.RecursionLevel
                        );
                }
            }
        }
    }
}

Örnek kod parçasında uspGetManagerEmployess özelliği çağırılmış ve ManagerID değeri 16 olan Employee listesinin getirilmesi sağlanmıştır. Bu işlemin arkasından da elde edilen sonuç kümesi üzerinden basit bir LINQ sorgusu çalıştırılmış ve FirstName alanının ilk harfi M olanların listelenmesi sağlanmıştır. Hemen bir hatırlatma yapalım; SQL tarafındaki uspGetManagerEmployees Stored Procedure' ünün çalıştırılması context nesne örneği üzerinden uspGetManagerEmployess özelliğinin çağırılması ile birlikte gerçekleşmektedir. Son olarak uygulamayı çalıştırdığımızda aşağıdaki çıktıyı elde ettiğimizi görürüz.

Böylece geldik bir yazımızın daha sonuna. Ado.Net Entity Framework 4.0 ile ilişkili yenilikleri öğrendikçe sizlerede aktarmaya çalışıyor olacağım. Bu konuda ilk elden bilgi almak isteyen arkadaşlarımızın Ado.Net Team Blog' unu mutlaka takip etmesini öneririm. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

SPAndComplexType.rar (36,40 kb)

Screencast - Ado.Net Entity Framework 4.0 Yenilikleri

Çarşamba, 28 Ekim 2009 05:07 by bsenyurt

Merhaba Arkadaşlar,

Ado.Net Entity Framework 4.0 versiyonunda, daha önceki sürüme göre önemli yenilikler ve düzeltmeler bulunmakta. Bunlardan birisi Model diagramından yararlanarak veritabanının oluşturulabilmesi Wink Bu sayede Entity tasarımlarının veritabanına aktarılmasını sağlayabilimekteyiz ki bu bir önceki sürümde olmasını istediğimiz yegane özelliklerden birisiydi. Bu önemli eklenti dışında, nesne isimlerinin çoğullaştırılması veya tekilleştirilmesi, Foreign Key alanlarının Entity tipleri içerisine istendiğinde Property olarak alınabilmesi veya Complex Type kullanımı gibi yeniliklerde yer almakta. Daha fazlası için sizleri görsel dersime davet etmek istiyorum.

   

Yeni görsel derslerde görüşmek dileğiyle hepinize mutlu günler dilerim.

Ado.Net Entity Framework' de Lazy ve Eager Loading

Perşembe, 16 Nisan 2009 15:41 by bsenyurt

Merhaba Arkadaşlar,

Bildiğiniz üzere uzun bir süre önce Microsoft, LINQ to SQL yerine Ado.Net Entity Framework ile ilerleme kararı aldı. Bu konu ile ilişkili olaraktan okuduğum hemem hemen bütün kitaplarda Ado.Net' in geleceğinde önemli bir yere sahip olan Ado.Net Entity Framework alt yapısının geliştiriciler tarafından asla ihmal edilmemesi gerektiğide sıkça vurgulanmakta. Peki günlüğüme konu olan mesele nedir?

Aslında günlüğe yazmadan önce odaklandığım nokta, aralarında master-detail ilişki bulunan tablo verilerinin, LINQ to SQL tarafında nasıl yüklendiğinin incelenmesiydi. Dolayısıyla önce LINQ to SQL tarafındaki duruma bir göz atmakya yarar var. İşe öncelikle basit bir Console uygulamasında kobay nesnelerimizden Northwind veritabanını kullanarak başlayabiliriz. Burada Visual Studio 2008 üzerinde ve .Net Framework 3.5 tabanlı bir geliştirme yaptığımızı belirtelim. Söz konusu uygulamamızda kullanacağımız LINQ to SQL diagramının içeriği aşağıdaki gibi tasarlanabilir.

Örnekte Category ve Product tablolarına ait tipleri göz önüne almaktayız. Buna göre "Kategoriler ve bu kategorilerdeki toplam ürün sayılarını öğrenmek" gibi basit bir sonuç kümesi elde etmek istediğimizde aşağıdakine benzer program kodlarını ele alacağımız muhtemeldir.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BlogSample
{
    class Program
    {
        static void Main(string[] args)
        {
            using (NorthwindDataContext northContext = new NorthwindDataContext())
            {
                // northContext.Log = Console.Out; // İsterseniz SQL sorgularını Console çıktısından da takip edebilirsiniz.
               
                foreach (Category category in northContext.Categories)
                {
                    Console.WriteLine("Category Name :{0} ({1})", category.CategoryName, category.Products.Count);
                }
            }
        }
    }
}

Programın bu haliyele çıktısı aşağıdaki gibidir.

Aslında istediğimizi elde etmiş görünüyoruz. Cool

Gayet doğal olarak foreach döngüsü içerisinde bir kategoriye bağlı ürün sayıları bulunurken Count özelliğinden yararlanılmaktadır. Ancak bu durumda Lazy Loading adı verilen durum oluşmakta ve SQL tarafına bakıldığında oldukça fazla sorgunun çalıştığı görülmektedir. Öyleki yukarıdaki kod çıktısı SQL Profiler yardımıyla incelendiğinde ilk sırada aşağıdaki sorgunun çalıştığı görülür.

Görüldüğü üzere ilk olarak tüm kategoriler Select sorgusu ile çekilmektedir. Ancak iş bundan sonra biraz daha dikkate değer bir hal alır. Nitekim her kategoriye ait ürün sayıları Products özelliği üzerinden elde edilmek istenmektedir. Bu durumda SQL tarafında her bir kategori satırı için birer sorgu cümlesi daha çalıştırılır.

Bu bir kaç satırlık veri için önemli gözükmese de, büyük boyutlu veriler ile çalışıldığı durumlarda önemli performans kayıplarına neden olabilir. Bu nedenle istenirse Eager Loading isimli bir teknikten de yararlanılabilir. Tek yapılması gereken kod tarafına aşağıdaki değişiklikleri eklemektir.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;

namespace BlogSample
{
    class Program
    {
        static void Main(string[] args)
        {
            using (NorthwindDataContext northContext = new NorthwindDataContext())
            {
                // northContext.Log = Console.Out; // İsterseniz SQL sorgularını Console çıktısından da takip edebilirsiniz.
                DataLoadOptions loadOption = new DataLoadOptions();
                loadOption.LoadWith<Category>(c => c.Products);
                northContext.LoadOptions = loadOption;

                foreach (Category category in northContext.Categories)
                {
                    Console.WriteLine("Category Name :{0} ({1})", category.CategoryName, category.Products.Count);
                }
            }
        }
    }
}

Bu durumda da kod aynı sonuçları verir ve SQL tarafındaki çıktıya bakıldığına tek bir sorgunun çalıştırıldığı gözlemlenebilir.

Ancak en iyi performans için bu teknikler yerine Anonymous Type kullanılması çok daha doğrudur. Öyleki sorguda istenen sadece kategori adları ve o kategoriye bağlı ürünlerin toplam sayılarıdır. Oysa ki sorgu cümlelerine bakıldığında o anda gerekli olmayan tablo alanlarının da hesaba katıldığı görülmektedir. Dolayısıyla sorgunun sadece, CategoryName ve buna bağlı Product satırlarının toplam sayısını bulacak şekilde iyileştirilebilmesi gerekmektedir. Aslında burada LINQ sorgularının defered execution adı verilen "gerektiği yerde sorguyu gönder" sistemide önemlidir. Kısaca LINQ sorgusunun yazıldığı satırda değilde, ilk kullanıldığı yerde SQL cümlesinin gönderilmesinden bahsediyoruz. Lafı fazla uzatmadan kod tarafındaki değişikliklerimize bakalım.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;

namespace BlogSample
{
    class Program
    {
        static void Main(string[] args)
        {
            using (NorthwindDataContext northContext = new NorthwindDataContext())
            {
                var resultSet = from c in northContext.Categories
                                select new
                                {
                                    c.CategoryName,
                                    c.Products.Count
                                };
                foreach (var result in resultSet)
                {
                    Console.WriteLine("Category Name :{0} ({1})", result.CategoryName,result.Count.ToString());
                }
            }
        }
    }
}

Bu durumda SQL tarafına giden sorgunun aşağıdaki gibi olduğu görülebilir.

Dikkat edileceği üzere sadece istediğimiz alanlar sorguya dahil edilmiştir.

Derken rüyadan uyanırım ve birden aklıma artık Ado.Net Entity Framework konulu bir yazı yazacağım gelir.Embarassed

Bakalım o tararfa Lazy Loading, Eager Loading durumları nasıl ele alınabilir. Bu kez projede kullanacağımız tiplerin EDM(Entity Data Model) diagramındaki görüntüsü aşağıdaki gibidir. Aynen yukarıda örneklerde olduğu gibi Category ve Product tablolarını ele alıyor olacağız.

Şimdi ilk kod parçamızı geliştirelim.

using (NorthwindEntities entity = new NorthwindEntities())
            {
                foreach (var category in entity.Categories)
                {
                    Console.WriteLine("Category Name :{0}({1})",category.CategoryName,category.Products.Count);
                }
            }

SQL Profiler'a geçmeden önce uygulama çalıştırıldığında aşağıdaki sonuç ile karşılaşılır.

Dikkatli gözlerden, ürün sayılarının 0 olarak geldiği kaçmayacaktır. SQL Profiler aracı ile gönderilen sorguya bakıldığındaysa sadece kategorilerin çekildiği ancak ürün sayılarının bulunması ile ilişkili bir şey yapılmadığı gözlemlenebilir.

Aslında bu son derece doğaldır nitekim Ado.Net Entity Framework modelinde Lazy Loading' in bilinçli ve açık bir şekilde yapılması istenmektedir. Dolayısıyla geliştiricinin gerçekten Lazy Loading yapmak istediğini kod tarafında belirtmesi gerekir. Peki bu nasıl yapılır? Aşağıdaki basit kod parçasında olduğu gibi...

using (NorthwindEntities entity = new NorthwindEntities())
            {
                foreach (var category in entity.Categories)
                {
                    if(!category.Products.IsLoaded)
                        category.Products.Load();
                    Console.WriteLine("Category Name :{0}({1})",category.CategoryName,category.Products.Count);
                }
            }

SQL Profiler' a bakıldığında aşağıdaki çıktı ile karşılaşılır.

İlk olarak kateogoriler çekilmiş sonrasında ilk 5 kategori için sırasıyla ürünlere ait sorgular çalıştırılmıştır. Ardından ise Category tablosu için yine bir Select çalıştırılmakta ve kalan 3 kategorinin her biri için tekrardan ürün sorguları yürütülmektedir.(Bu yazıda anlattıklarımı bire bir uygulamanızı, SQL Profiler aracaı yardımıylada irdelemeye çalışmanızı şiddetle öneririm.) 

Buradaki kod parçasında her bir kategori için buna bağlı ürünlerinde Products özelliği ile işaret edilen referansa yüklenmesi istendiği Load metodu yardımıyla belirtilmektedir. Bu kod ile Lazy Loading gerçekleştirilmiş olmaktadır. Peki ya Eager Loading? Eager Loading için aşağıdaki kod parçasını kullanmak yeterli olacaktır.

using (NorthwindEntities entity = new NorthwindEntities())
            {
                foreach (var category in entity.Categories.Include("Products"))
                {                   
                    Console.WriteLine("Category Name :{0}({1})",category.CategoryName,category.Products.Count);
                }
            }

Aslında tek yaptığımız Categories özelliği üzerinden Include metodunu çağırmak ve Products değerini vermek olmuştur. Buna göre SQL tarafında aşağıdaki sorgunun çalıştırıldığı görülür.

Elbette yine istediğimizi alamadık. Nitekim sadece kategori adı ve ürün sayılarını elde etmek gibi bir amacımız vardı. LINQ to SQL tarafında yapmış olduğumuz iyileştirmeyi Ado.Net Entity Framework tarafında da Include metodunu hesaba katarak ve yine Anonymous Type kullanarak gerçekleştirebiliriz.

using (NorthwindEntities entity = new NorthwindEntities())
            {
                var resultSet = from c in entity.Categories.Include("Products")
                                select new
                                {
                                    c.CategoryName,
                                    c.Products.Count
                                };
                foreach (var result in resultSet)
                {
                    Console.WriteLine("Category Name :{0}({1})", result.CategoryName, result.Count);
                }              
            }

Sonuç olarak SQL tarafına aşağıdaki sorgu cümlesi gönderilecektir.

Görüdüğü üzere sadece CategoryName ve sub query ile birlikte ürün sayıları hesaba katılmaktadır.

Böylece geldik bir günlük yazımızın daha sonuna. Bu yazımızda LINQ to SQL ve Ado.Net Entity Framework tarafında Lazy ve Eager Loading kavramlarını değerlendirmeye çalıştık. Umarım yararlı olmuştur.

Görüşmek dileğiyle...

Programming Entity Framework

Perşembe, 16 Nisan 2009 10:20 by bsenyurt

Çok yakın bir zamanda O'Reilly yayınlarından çıkmış olan 828 sayfalık bu kitap, Ado.Net Entity Framework ile ilişkili dolu dolu bilgiler içeriyor. Amazon' dan tedarik edebileceğiniz bu kitap özellikle yeni dönemde baş ucumuzda durması gereken kaynaklardan birisi.

Julie Lerman tarafından yazılmış(blog adresi) olan kitaba daha ilk bölümden itibaren bağlanmamak elde değil. Öncelikle EDM(Entity Data Model) kavramı üzerinde güzel bir giriş yapıp, sorunlara ve nedenlere cevaplar veriliyor. Sonrasında ise Microsoft' un Ado.Net tarafında söz konusu EDM modelini Entity Framework ile nasıl uyguladığına değinilmeye başlanıyor ve siz ısındıktan sonrada teknik detaylara geçilerek profesyonel seviyede ilerleniyor.

Ben kütüphanemin baş köşesine koydum. Tabi teknoloji tekrardan yeni değişiklikleri beraberinde getirinceye dek.