RSS Feeds

28 Eylül 2010 Salı

Dinamik Sorgu Çalıştıran Prosedürlerin Veri Modeline Dahil Edilmeleri

ADO.NET Entity Framework’ün ilk adı geçtiği zamanlar, LINQ to SQL üzerine neler getireceği ve biz geliştiricilere ne gibi yararlar sağlayacağı benim için önemli bir merak konusuydu. O günlerin üzerinden çok zaman geçti, EF 4.0 sürümüne ulaştı ve hepimiz, neden Microsoft’un LINQ to SQL aracını “Temel seviyede bir ORM aracı” olarak bırakıp EF üzerine yoğunlaştığını anladık. Geçen zaman içinde LINQ to SQL’de ele alınamayan fakat EF’de çözüm bulmuş birçok senaryoyla karşılaştığımı itiraf etmeliyim ve bu senaryoları zaman zaman Blog üzerinden sizlerle de paylaşmak istiyorum. Bir cümleyle özetleyecek olursak, bu yazımızda, sp_executesql çağrısını içeren Stored Procedure’lerin, veri modeli (EDMX,DBML) içerisinde ele alınma durumlarına göz atacak ve bu noktada LINQ to SQL ile EF arasındaki farklılıklardan birini de ortaya çıkaracağız.

Öncelikle örneğin daha iyi anlaşılabilmesi için bahsi geçen Stored Procedure’lerin nasıl göründüğünü incelemekte fayda var.

CREATE PROC GetProducts(@CategoryList VARCHAR(255))
AS
BEGIN
    DECLARE @query NVARCHAR(500)    = N'SELECT UrunId,UrunAdi FROM Urun WHERE AltKategoriId IN ('+ @CategoryList + ')'
    EXEC sp_executesql @query
END

Prosedürü oluşturan TSQL kodları incelendiğinde, parametre olarak gelen metinsel ifadenin dinamik oluşturulan sorguya Concat ile eklendiği görülmektedir. Böylece GetProducts prosedürüne yapılan çağrılarda tek bir metinsel parametre içerisinde, istenildiği kadar AltKategoriId değeri virgüllerle ayrılarak belirtilebilmektedir.

Test 1 : LINQ to SQL

Veritabanında oluşturulan prosedür Server Explorer penceresinden sürükle-bırak yaparak veri modeline dahil edildiğinde, ilgili prosedüre çağrı yapacak bir metodun oluştuğu görülmektedir.

1

Ancak dikkat edilirse, metodun Return Type özelliğinde (None) yazmakta olup, değeri de değiştirilememektedir:( Eğer GetProducts prosedürü içinde sp_executesql ile dinamik oluşturulan bir sorgu yerine bir SELECT ifadesi bulunuyor olsaydı, dönüş tipi (Auto-generated Type) olacaktı. Hatta istenirse, otomatik oluşturulan sınıf yerine modeldeki herhangi bir Entity Class da kullanılabilecekti. Ne yazık ki sorgu dinamik oluşturulduğunda, prosedürün modele doğru bir şekilde yerleştirilemediği görülmektedir. Design kısmında işler böyle, peki ya üretilen Custom DataContext sınıfının kodlarını incelersek bir sonuca varabilir miyiz?

Aşağıda, üretilen GetProducts metodunun kodları görülmektedir.

[global::System.Data.Linq.Mapping.FunctionAttribute(Name="dbo.GetProducts")]
        public int GetProducts([global::System.Data.Linq.Mapping.ParameterAttribute(Name="CategoryList", DbType="VarChar(255)")] string categoryList)
        {
            IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), categoryList);
            return ((int)(result.ReturnValue));
        }

Hmmm, üretilen metodun dönüş tipi int olarak ayarlanmış görünüyor. Demek ki buradan şöyle bir yorum yapılabilir :

sp_executesql kullanılarak dinamik sorgu oluşturan stored procedure’ler, LINQ to SQL ile oluşturulan veri modellerine dahil edilemezler.”

Sonuç olarak LINQ to SQL testi geçememiş görünüyor. Bakalım Entity Framework 4.0 bu prosedürü veri modeline dahil edebilecek mi?

Test 2 : ADO.NET Entity Framework 4.0

Benzer bir testi ADO.NET EF 4.0 kullanarak yapabilmek için, veri modeli oluşturulduktan sonra öncelikle Model Browser üzerinden Function Import yapılması gerekmektedir.

2

Function Import seçeneğine tıklandığında, prosedürü çağırmak için üretilecek metotla ilgili bir takım bilgilerin girileceği dialog ekranı açılmaktadır. Bu ekranın nasıl kullanıldığı bu makalenin konusu olmadığından dolayı, bu kısımla ilgili araştırmayı siz değerli okurlara bırakıyorum.

Bu noktada önemli bir ayrıntıyı hatırlatmakta fayda vardır : Prosedürün çalışması sonrası oluşan sonuç kümesinin şeması! Örnekte kullanmak üzere oluşturulan prosedürün kodları incelendiğinde, aşağıdaki gibi bir SELECT ifadesinin çalışma zamanında oluşacağı anlaşılmaktadır :

”SELECT UrunId,UrunAdi FROM Urun WHERE AltKategoriId IN (1,2,3,4)”

Görüldüğü üzere oluşan sonuç kümesi 2 kolona sahiptir. Yani bu durumda prosedürün oluşturduğu sonuçları uygulama içinde ele alabilmek için UrunId ve UrunAdi özelliklerine sahip bir sınıfa ihtiyaç duyulacaktır. Eğer oluşturulan SELECT ifadesi, aşağıdaki gibi, tüm tablonun tüm kolonlarını içerecek şekilde olsaydı durum ne olurdu?

”SELECT * FROM Urun WHERE AltKategoriId IN (1,2,3,4)”

Dilerseniz bu iki durumu iki ayrı başlık altında inceleyelim.

a) Mevcut bir Entity’nin şemasına uyan durumlar

Eğer  dinamik üretilen SELECT ifadesinde tüm kolonlar seçiliyorsa (* karakteri olması ya da kolonların tek tek belirtilmesi farketmez), Function Import ekranında aşağıdaki düzenlemenin yapılması yeterli olacaktır. Tabi bunun için öncelikle ilgili entity sınıfının veri modeline eklenmiş olması gerektiği unutulmamalıdır.

4

a) Mevcut bir Entity’nin şemasına uymayan durumlar

Eğer prosedür tarafından üretilen sonuç kümesi, mevcut bir entity’nin şemasına uymuyorsa, bu durumda  ComplexType özelliğinden faydalanarak, uygun bir dönüş tipi oluşturulabilir. Ancak burada istisnai bir durum söz konusudur. Dinamik sorgu oluşturmadan bir SELECT çalıştırıyor olsaydı, önce Get Column Information, sonra da Create New Complex Type butonlarına tıklayarak otomatik olarak ComplexType üretilebilirdi. Fakat, GetProducts prosedürü, sp_executesql aracılığıyla dinamik SELECT ifadesi çalıştırdığından, sonuç kümesinin şeması (Column Information) elde edilememekte, dolayısıyla ComplexType  otomatik üretilememektedir. Bu durum Get Column Information butonuna tıklandığında açıkça görülmektedir.

3

Neyse ki, çözümsüz değiliz. LINQ to SQL ‘in çözüm sunmadığı bu problem, EF içerisinde ComplexType’ın elle üretilmesi sonrasında halledilebiliyor. Bunun için öncelikle Model Browser ekranından Create Complex Type seçeneğine tıklanarak, sonuç kümesinin şemasına uygun bir tip oluşturulması gerekmektedir. Ben sınıf isminde “[ProsedurAdi]Result” notasyonunu kullandığımdan, Complex Type’ı GetProductsResult olarak isimlendirdim.

5

Sınıf oluşturulduktan sonra, GetProductsResult üzerine sağ tuşla tıklandığında açılan menüden, Add > Scalar Property seçilmeli ve SELECT ifadesinden dönen her bir kolona karşılık, uygun veri tipinde bir Property eklenmelidir. 

6

Son olarak tekrar GetProducts prosedürü için Function Import ekranına geçildiğinde, bu defa Complex Type’ın açılan listeden seçilebildiği görülmektedir. Bu düzenleme sonrasında, prosedüre çağrı yapacak metot başarılı bir şekilde üretilmiş, diğer bir deyişle, GetProducts prosedürü Entity Data Model içerisine yerleştirilmiş olmalıdır.

7

Aşağıda da görüldüğü üzere, prosedür başarılı bir şekilde çalışmakta ve sonuçlar, oluşturulan GetProductsResult sınıfı üzerinden bir diziye alınabilmektedir. Bu da işlemin başarılı olduğunu kanıtlarWink

8

Yazının sonuna gelirken, LINQ to SQL ‘de çözülemeyen birçok sorunun, EF sayesinde kolaylıkla çözülebildiğinin altını tekrar tekrar çizmek gerekir. Yukarıda bahsedilen senaryo da bunlardan sadece biridir. İlerleyen zamanlarda buna benzer başka tecrübeleri de tartışmak dileğiyle, şimdilik herkese iyi çalışmalar diliyorum.

0 yorum:

Yorum Gönder