https://www.buraksenyurt.com/Burak Selim Şenyurt - LINQ to SQL2011-11-26T10:12:43+00:00Matematik Mühendisi Bir Bilgisayar Programcısının NotlarıBurak Selim SenyurtBlogEngine.Net Syndication Generatorhttps://www.buraksenyurt.com/opml.axdBurak Selim SenyurtMatematik Mühendisi Bir Bilgisayar Programcısının Notlarıtr-TRBurak Selim Şenyurt0.0000000.000000https://www.buraksenyurt.com/post/Tek-Fotoluk-Ipucu-42(ExecuteQuery-ile-Injection-dan-Korunmak)Tek Fotoluk İpucu-42(ExecuteQuery ile Injection' dan Korunmak)2011-11-26T03:15:00+00:00bsenyurt<p>Merhaba Arkadaşlar,</p>
<p>LINQ to SQL kullandığımız durumlarda bildiğiniz gibi dışarıdan SQL sorgularını da icra ettirebilmekteyiz. Bu amaçla DataContext tipinin ExecuteQuery metodu kullanılmakta. Ancak özellikle SQL Injection saldırılarına karşı dikkatli olmamız gerekiyor. Bu nedenle söz konusu metodun placeholder kullanımına izin veren versiyonunu ele almamızda yarar olduğu kanısındayım. Nasıl mı? <img class="wlEmoticon wlEmoticon-winkingsmile" style="border-bottom-style: none; border-left-style: none; border-top-style: none; border-right-style: none" src="http://www.buraksenyurt.com/pics/wlEmoticon-winkingsmile_59.png" alt="Winking smile" /></p>
<p><a href="http://www.buraksenyurt.com/pics/PhotoTrick42.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 4px 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="PhotoTrick42" src="http://www.buraksenyurt.com/pics/PhotoTrick42_thumb.png" border="0" alt="PhotoTrick42" width="593" height="533" /></a></p>
<p><a href="http://www.buraksenyurt.com/pics/2011%2f7%2fExecuteQueryAndInjection.rar">ExecuteQueryAndInjection.rar (52,04 kb)</a></p>2011-11-26T03:15:00+00:00c#linq to sqlexecutequerysql injectionsqlbsenyurthttps://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=9ff6fe5a-377d-4905-b4a3-c2a0ea5e555e0https://www.buraksenyurt.com/trackback.axd?id=9ff6fe5a-377d-4905-b4a3-c2a0ea5e555ehttps://www.buraksenyurt.com/post/Tek-Fotoluk-Ipucu-42(ExecuteQuery-ile-Injection-dan-Korunmak)#commenthttps://www.buraksenyurt.com/syndication.axd?post=9ff6fe5a-377d-4905-b4a3-c2a0ea5e555ehttps://www.buraksenyurt.com/post/LINQ-to-SQL-e28093-EF-40-(Aradaki-9-Farkc4b1-Bulun)LINQ to SQL – EF 4.0 (Aradaki 9 Farkı Bulun)2010-09-20T15:07:00+00:00bsenyurt<p>Merhaba arkadaşlar,</p>
<p>Evet çok doğru. Hiç bu kadar kısa ve öz yazmamıştım daha önceden. Ama zaman zaman bu kadar kısa yazıp çok fazla şey ifade edilebileceğine de inanmaktayım <img title="Wink" src="http://www.buraksenyurt.com/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" border="0" alt="Wink" /> Hani ilk bakışta herşeyin şak diye kafanızda yer ettiği tablolar olur ya…Bu blog girdisinde de benzer bir resmi göreceğinizi düşünüyorum. Aslında olay bundan çok çok uzun zaman önce gelen bir soru üzerine meydana geldi.</p>
<p><strong>“Entity Framework ile LINQ to SQL arasındaki temel ve belirgin farklılıklar nelerdir? Hangi durumlarda hangisi tercih edilmelidir?” </strong></p>
<p>Soruyu araştırmak ve cevap vermek için aradan baya bir zaman geçti ancak yaptığım incelemeler sonucunda aşağıdaki temel ayrımları tespit ettiğimi ifade edebilirim. Hatta ortak oldukları noktaları da bu tabloda görebilirsiniz.</p>
<table style="width: 577px;" border="1" cellspacing="0" cellpadding="2">
<tbody>
<tr>
<td width="358" valign="top"><strong>Kavram</strong></td>
<td width="103" valign="top"><strong>EF 4.0</strong></td>
<td width="114" valign="top"><strong>LINQ to SQL</strong></td>
</tr>
<tr>
<td width="358" valign="top"><strong>Sql Server Harici Veritabanı Desteği</strong></td>
<td width="103" valign="top">Var</td>
<td width="114" valign="top">Yok gibi</td>
</tr>
<tr>
<td width="358" valign="top"><strong>Doğrudan veritabanı bağlantısı</strong></td>
<td width="103" valign="top">Yok</td>
<td width="114" valign="top">Var</td>
</tr>
<tr>
<td width="358" valign="top"><strong>Çoklu tablodan kalıtım(Multiple Table Inheritance)</strong></td>
<td width="103" valign="top">Var</td>
<td width="114" valign="top">Yok</td>
</tr>
<tr>
<td width="358" valign="top"><strong>Birden fazla tablodan tek bir Entity üretmek</strong></td>
<td width="103" valign="top">Var</td>
<td width="114" valign="top">Yok</td>
</tr>
<tr>
<td width="358" valign="top"><strong>Conceptual Schema Definition Language(CSDL)</strong></td>
<td width="103" valign="top">Var</td>
<td width="114" valign="top">Yok</td>
</tr>
<tr>
<td width="358" valign="top"><strong>Storage Schema Definition Language(SSDL)</strong></td>
<td width="103" valign="top">Var</td>
<td width="114" valign="top">Yok</td>
</tr>
<tr>
<td width="358" valign="top"><strong>Mapping Schema Language(MSL)</strong></td>
<td width="103" valign="top">Var</td>
<td width="114" valign="top">Yok</td>
</tr>
<tr>
<td width="358" valign="top"><strong>Lazy Loading</strong></td>
<td width="103" valign="top">Var</td>
<td width="114" valign="top">Var</td>
</tr>
<tr>
<td width="358" valign="top"><strong>Stored Procedures</strong></td>
<td width="103" valign="top">Var</td>
<td width="114" valign="top">Var</td>
</tr>
</tbody>
</table>
<p>Tabi bazı noktalarda çok keskin ayrımlar olduğunu ifade edebiliriz. Söz gelimi <strong>LINQ to SQL </strong>takımı doğrudan <strong>.Net’ </strong>in <strong>SQL </strong>yönetim bileşenlerini kullanmayı tercih etmektedir. Sanıyorum ki <strong>C# </strong>takımının üyelerinin <strong>LINQ to SQL</strong>’ i geliştirmiş olmasının bunda büyük rolü vardır <img title="Wink" src="http://www.buraksenyurt.com/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" border="0" alt="Wink" /> Öyleki <strong>EF </strong>takımı doğrudan veritabanı erişimi veya <strong>SQL</strong> nesnelerini<strong>(SqlConnection, SqlCommand vb…)</strong> kullanmak yerine <strong>Conceptual </strong>bir modeli baz alarak depolama ve kütüphane nesnelerinin eşleştirilmesinde <strong>XML </strong>bazlı daha esnek bir yapıyı tercih etmiştir. Dilerseniz <strong>LINQ to SQL’</strong> in arka tarafına kısaca bir bakalım ve<strong> SQL</strong> nesne <strong>kullanımını(ya da bağımlılığını) </strong>görmeye çalışalım.</p>
<p>Örnek olarak <strong>Northwind</strong> veritabanını kullandığımız bir <strong>LINQ to SQL </strong>sınıf yapsında üretilen <strong>NorthwindDataContext </strong>tipinin içeriğinde aşağıdaki <strong>yapıcı metodlar(Constructors) </strong>hemen göze çarpacaktır.</p>
<p><a href="http://www.buraksenyurt.com/pics/blg228_DataContext.gif"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="blg228_DataContext" src="http://www.buraksenyurt.com/pics/blg228_DataContext_thumb.gif" border="0" alt="blg228_DataContext" width="643" height="506" /></a></p>
<p>Dikkat edileceği üzere <strong>IDbConnection</strong> <strong>interface</strong> tipini kullanan iki yapıcı metod versiyonu söz konusudur. Eğer <strong>IDBConnection</strong>’ dan türeyen <strong>.Net </strong>tiplerine bakarsak aşağıdaki sonuçlar ile karşılaşırız.</p>
<p><a href="http://www.buraksenyurt.com/pics/blg228_IDbConnection.gif"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="blg228_IDbConnection" src="http://www.buraksenyurt.com/pics/blg228_IDbConnection_thumb.gif" border="0" alt="blg228_IDbConnection" width="307" height="134" /></a></p>
<p>Dikkat edeceğiniz üzere <strong>OdbcConnection</strong>, <strong>OleDbConnection </strong>ve <strong>SqlConnection </strong>tipleri söz konusudur. Buradan <strong>DataContext </strong>türevli olan <strong>NorthwindDataContext </strong>tipinin üretimi sırasında mutlaka <strong>SQL</strong> tarafına bir bağımlılığın olduğunu en azından<strong> .Net</strong> içerisinde var olan <strong>provider</strong> yapısının ele alındığını görebiliriz.</p>
<p>Bu ve diğer farklılıkları aslında ilerleyen yazılarımızda incelemeye çalışıyor olacağım. Siz şimdilik yukarıdaki klavuzu gözünüze kestirin. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2010-09-20T15:07:00+00:00linq to sqlado.net entity framework 4.0ef 4.0entity frameworkbsenyurthttps://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=3ba48c9b-16cd-4a67-b1fb-9e787859df070https://www.buraksenyurt.com/trackback.axd?id=3ba48c9b-16cd-4a67-b1fb-9e787859df07https://www.buraksenyurt.com/post/LINQ-to-SQL-e28093-EF-40-(Aradaki-9-Farkc4b1-Bulun)#commenthttps://www.buraksenyurt.com/syndication.axd?post=3ba48c9b-16cd-4a67-b1fb-9e787859df07https://www.buraksenyurt.com/post/Linq-To-Sql-Arka-Planda-Neler-Oluyor-bsenyurt-com-danLinq To Sql : Arka Planda Neler Oluyor?2007-12-19T10:00:00+00:00bsenyurt<p>Değerli Okurlarım Merhabalar,</p>
<p>Veritabanı(Database) nesnelerinin programatik ortamda sınıf gibi tipler(Type) ve metod benzeri üyeler(Members) ile ifade ediliyor olması, bu tiplere ait nesne örnekleri üzerinden sorgulalamalar yapılabilmesi ihtiyacınıda ortaya çıkartmıştır. Bir veritabanı nesnesinin programatik taraftaki karşılığının nesne yönelimli(Object Oriented) bir dilde geliştirilmesi son derece kolaydır. Örneğin bir <strong>tablo(Table)</strong> göz önüne alındığında, bu tablonun kendisi bir <strong>sınıf(Class)</strong> olarak tasarlanabilir. Benzer şekilde, tablo içerisindeki <strong>alanlar(Fields)</strong> sınıf içinde yer alan birer <strong>özellik(Property) </strong>olarak düşünülebilir.</p>
<p>Basit <strong> CRUD(CreateRetrieveUpdateDelete)</strong> işlemleri, <strong>varlık sınıfı(Entity Class)</strong> diyebileceğimiz tipin birer üye metodu olarak düşünülebilir. Tabloda yer alan kolonların bazı niceleyicileri(örneğin Null değer içerip içermedikleri, primary key olup olmadıkları vb...) sınıfın kendisi ve üyeleri için birer <strong>nitelik(Attribute)</strong> olarak tasarlanabilir. Ne varki bu eşleştirme kolaylığı dışında, programatik tarafta yer alan nesnel yapılar üzerinde, SQL cümlelerine benzer ifadeler ile sorgulamalar yapmak kolay değildir. Nitekim, programatik tarafın SQL benzeri cümelere karşılık gelen fonksiyonellikleri ele alıyor olması gerekmektedir. <strong>LINQ(Language Integrated Query)</strong> mimariside, temel anlamda programatik tarafta yazılan ifadeleri arka planda metodlar, <strong>temsilciler(Delegates)</strong> yardımıyla kurduğu bir modele dönüştürmektedir. <strong>LINQ</strong>' in kullanıldığı alanlar göz önüne alındığında en popüler seçeneklerden biriside<strong> LINQ to SQL</strong> mimarisidir.</p>
<p><strong>Language INtegrated Query to SQL</strong> mimarisi ile, <strong>varlık tipleri(Entity Types)</strong> üzerinden sorgular çalıştırılabilir. Basit anlamda, <strong>nesneler(Objects)</strong> üzerinde uygulanabilen LINQ sorguları, SQL tarafına ulaştıklarında ise bildiğimiz <strong>sorgu ifadelerine(Query Expressions) </strong>dönüşmektedir. Bilinen LINQ operatörlerinin veya metodlarının tamamının SQL tarafına uygulanamadığı veya henüz uygulanamadığı bir gerçektir. Nitekim programatik ortamın esnekliği nedeni ile, örneğin bir dizinin ters çevrilerek elemanları üzerinde döngüsel anlamda ilerlenebilmesinin, <strong>SQL</strong> tarafında karşılığının bulunması zordur(ki buda <strong>LINQ</strong> metodlarından olan <strong>Reverse</strong> fonksiyonelliğinin neden<strong> LINQ to SQL </strong>üzerinde kullanılamadığınıda açıklamaktadır).</p>
<p>Bu ana fikirlerden yola çıkarak makalemizdeki ana temamızın, SQL ifadelerine çevrilebilen <strong>LINQ</strong> operatörlerinin veya fonksiyonelliklerinin, arka planda ne şekilde tasarlandıklarını inceleyebilmektir. Bir başka deyişle basit ve karmaşık LINQ cümlelerinin, SQL tarafında ele alınabilen karşılıklarının ne olduklarını tespit edebilmektir. Bu araştırmadaki en büyük yardımcılarımız ise<strong> SQL Server Profiler</strong> ve<strong> Estimated Execution Plan araçları(Tools) </strong>olacaktır. <strong>SQL Server Profiler</strong> aracı kullanılarak, <strong>varlık(Entity)</strong> nesneleri üzerinde çalıştırılan <strong> LINQ</strong> ifadelerinin karşılığı olan SQL sorgu cümlelerini görmek mümkün olabilmektedir. Diğer taraftan <strong>Estimated Execution Plan</strong> aracı sayesinde, LINQ için arka tarafta çalıştırılan bir sorgu cümlesinin icra planının görülmesi ve alternatif ifadeler ile aralarındaki farklar tespit edilerek daha optimal yolların göz önüne alınması sağlanabilir.</p>
<p>Dilerseniz hiç vakit kaybetmeden örneklerimize geçerek devam edelim. Her zamanki gibi <strong>Visual Studio 2008 RTM </strong> üzerinden örnek kod parçalarımızı çalıştırıyor olacağız. Basit bir <strong> Console</strong> uygulaması üzerinden ilerlerken <strong>AdventureWorks</strong> ve <strong> Northwind veritabanlarındaki(Database) </strong>bazı tabloları kullanıyor olacağız. Bu anlamda<strong> AdventureWorks.dmbl</strong> ve <strong>Northwind.dmbl</strong> isimli <strong>DataBase Markup Language</strong> içeriklerimiz aşağıdaki ekran görüntülerinde yer aldığı gibi olacaktır.</p>
<p><strong>AdventureWorks.dbml;</strong></p>
<p><img src="/makale/images/mk236_1.gif" alt="" width="462" height="325" border="0" /></p>
<p><strong>AdventureWorks</strong> veritabanından örnek sorgulamalar için <strong>ProductCategory</strong>, <strong>ProductSubCategory</strong>, <strong>Product</strong>, <strong>SalesPerson</strong> ve <strong>SalesOrderHeader</strong> tabloları ele alınmaktadır.</p>
<p><strong>Northwind.dmbl;</strong></p>
<p><img src="/makale/images/mk236_2.gif" alt="" width="465" height="142" border="0" /></p>
<p><strong>Northwind</strong> veritabanından ise <strong> Customer</strong> ve <strong>Supplier</strong> tabloları ele alınmaktadır.</p>
<p>İlk olarak aşağıdaki gibi basit bir <strong> LINQ</strong> ifadesi ile başlayalım.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">AdventureWorksDataContext adw = new AdventureWorksDataContext();
var mClassProducts = from prd in adw.Products
where prd.Class == "M"
select prd;
int mCount = mClassProducts.Count();
double mSumListPrice = mClassProducts.Sum<Product>(prd => (double)prd.ListPrice);
Console.WriteLine(mCount.ToString());
Console.WriteLine(mSumListPrice.ToString("C2"));</pre>
<p>İlk olarak AdventureWorksDataContext nesnesi örneklenmektedir. Sonrasında <strong>mClassProducts</strong> isimli alana atanan ifadede <strong>AdventureWorksDataContext</strong> içerisinde yer alan <strong> Products</strong> özelliğinin karşılığı olan generic <strong>Table<Product></strong> tipi ele alınmaktadır. Buna göre <strong>Product</strong> nesne örneklerinden, <strong> Class</strong> özelliklerinin(Properties) değerleri <strong>M</strong> olanlar seçilmektedir. Daha önceki makalalerdede belirttiğimiz gibi <strong>var</strong> anahtar kelimesi ile tanımlanmış olan değişkene atanan bu ifade çalışma zamanında hemen yürütülmemektedir. İcra işlemi için bir <strong>for</strong> iterasyonu olması yada ifade üzerinden örnekteki gibi <strong>Aggregate</strong> benzeri fonksiyonelliklerin çalıştırılması gerekmektedir. <strong>mCount</strong> alanının değeri, hazırlanan <strong>LINQ</strong> ifadesinde <strong>Count</strong> metodu uygulanarak elde edilmektedir. Bir başka ifadeyle, sınıfı <strong>M</strong> olan ürünlerin toplam sayısı bulunmaktadır. <strong> mSumListPrice</strong> alanına atanan değer ilede, sınıfı M olan ürünlerin <strong> liste fiyatlarının(ListPrice)</strong> toplamı elde edilir. Sonrasında ise bu sonuçlar ekrana yazdırılır. <strong>Çalışma zamanında(run time) </strong> uygulamanın çıktısı aşağıdaki gibi olacaktır.</p>
<p><img src="/makale/images/mk236_3.gif" alt="" width="298" height="89" border="0" /></p>
<p>Gelelim arka tarafta çalıştırılan SQL sorgu cümlelerine.<strong> SQL Server Profiler </strong>aracı kullanılarak yapılan çalışmada <strong>Count</strong> ve <strong>Sum</strong> <strong>aggregate</strong> metodlarının aşağıdaki gibi icra edildiği görülmektedir. Count fonksiyonunun çağırılması sonucu çalışan sorgu şu şekildedir;</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'SELECT COUNT(*) AS [value]
FROM [Production].[Product] AS [t0]
WHERE [t0].[Class] = @p0',N'@p0 nvarchar(1)',@p0=N'M'</pre>
<p>Burada dikkat edilmesi gereken noktalardan birisi <strong>Count(*)</strong> ifadesidir. Normal şartlar altında tavsiye edilen yöntemlerden birisi <strong>Count(ProductID) </strong>tarzında bir kullanım yapılması yönündedir. Bu tarz bir kullanımın performans yönünde avantaj sağladığı bilinmektedir. Nitekim LINQ tarafından gelen ifadeye göre Count(*) şeklinde bir <strong>SQL</strong> fonksiyonu kullanılmıştır. Diğer tarafan <strong>LINQ</strong> sorgusunun aşağıdaki gibi değiştirilmesi düşünülebilir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var mClassProducts = from prd in adw.Products
where prd.Class == "M"
select prd.ProductID;</pre>
<p>Dikkat edileceği üzere burada sadece <strong> ProductID</strong> alanları seçilmektedir. Bu ifade üzerinden <strong>Count</strong> metodu kullanılırsa <strong>SQL</strong> tarafında icra edilen operasyonun değişmediği, bir başka deyişle <strong>Count(*) </strong>çağrısı yapıldığı görülür.<strong> Sum</strong> fonksiyonunun çağırılması sonucu çalışan sorgu ise aşağıdaki gibidir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'SELECT SUM([t1].[value]) AS [value]
FROM (
SELECT CONVERT(Float,[t0].[ListPrice]) AS [value], [t0].[Class]
FROM [Production].[Product] AS [t0]
) AS [t1]
WHERE [t1].[Class] = @p0',N'@p0 nvarchar(1)',@p0=N'M'</pre>
<p>Dikkat edilecek olursa burada <strong>Sum</strong> işlemi için bir iç Select sorgusu daha yürütülmektedir. Aynı amaca yönelik olaraktan aşağıdaki gibi bir sorguda göz önüne alınabilir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">SELECT SUM(ListPrice) AS [Value]
FROM Production.Product
GROUP BY Class
HAVING Class='M'</pre>
<p>Burada <strong>Group By</strong> kullanımı gerçekleştirilmektedir. <strong>LINQ</strong>' in <strong>Sum</strong> metodunu neden farklı bir şekilde yorumladığı tartışılabilir. Sonuç itibariyle her iki sorgu cümlesininde <strong>beklenen icra planlarına(Esitamted Execution Plan) </strong>bakıldığında <strong>Products</strong> gibi sadece 504 satıra sahip olan küçük bir tabloda çok fazla bir fark olmadığı görülmektedir. Ancak tablo boyutunun artması halinde bu durumun belirgin performans farklılıklarına yol açıp açmayacağına bakılmalıdır. Aşağıdaki ekran görüntülerinde her iki sorgunun icra planlarına ait icra maliyetleri daha net bir şekilde görülmektedir.</p>
<p><img src="/makale/images/mk236_4.gif" alt="" width="638" height="557" border="0" /></p>
<p>Diğer <strong>LINQ to SQL</strong> operatörlerini inceleyerek devam edelim. Sıradaki <strong>LINQ</strong> ifadesinde, <strong>Contains</strong> metodu ele alınmaktadır. Contains metodu <strong>String</strong> sınıfına ait bir fonksiyondur. Aşağıdaki kod parçasında yer alan ifadeye göre <strong>Contains</strong> metodunun görevi, <strong> ProductNumber</strong> alanında <strong>PA</strong> hecesi olan <strong>Product</strong> nesne örneklerinin tespit edilmesinin sağlanmasıdır.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var allProducts = from prd in adw.Products
where prd.Class == null && prd.ProductNumber.Contains("PA")
select prd;
foreach (Product p in allProducts)
{
Console.WriteLine(p.ProductNumber + " " + p.Name);
}</pre>
<p>Sorgu ifadesi <strong>null</strong> değere sahip olan sınıflara ait ürünlerden, ürün numarasında <strong>PA</strong> hecesi geçenleri elde etmektedir. Yukarıdaki kod parçasını içeren <strong> Console</strong> uygulaması çalıştırıldığında aşağıdaki sonuçlar alınacaktır.</p>
<p><img src="/makale/images/mk236_5.gif" alt="" width="274" height="128" border="0" /></p>
<p>Bizim için asıl önem arz eden konu <strong> LINQ to SQL</strong> ifadesinin <strong>SQL</strong> sunucusuna nasıl gönderildiğidir. Hemen <strong>SQL Server Profiler</strong> aracına bakılırsa aşağıdaki sorgu cümlesinin çalıştırıldığı görülebilir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'SELECT
[t0].[ProductID], [t0].[Name]
, [t0].[ProductNumber], [t0].[MakeFlag]
, [t0].[FinishedGoodsFlag], [t0].[Color]
, [t0].[SafetyStockLevel], [t0].[ReorderPoint]
, [t0].[StandardCost], [t0].[ListPrice]
, [t0].[Size], [t0].[SizeUnitMeasureCode]
, [t0].[WeightUnitMeasureCode], [t0].[Weight]
, [t0].[DaysToManufacture], [t0].[ProductLine]
, [t0].[Class], [t0].[Style], [t0].[ProductSubcategoryID]
, [t0].[ProductModelID], [t0].[SellStartDate]
, [t0].[SellEndDate], [t0].[DiscontinuedDate]
, [t0].[rowguid], [t0].[ModifiedDate]
FROM [Production].[Product] AS [t0]
WHERE
([t0].[Class] IS NULL) AND ([t0].[ProductNumber] LIKE @p0)'
,N'@p0 nvarchar(4)',@p0=N'%PA%'</pre>
<p>Dikkat edileceği üzere programatik tarafta yapılan <strong>==null</strong> kontrolü <strong>SQL</strong> tarafında<strong> Is Null </strong>olarak, <strong>Contains</strong> metodu ise <strong>Like</strong> olarak çevrilmiştir. Buradaki Like ifadesine gönderilen <strong>%PA%</strong> değeri, içerisinde PA hecesi geçenleri ifade etmektedir. Öyleyse <strong>Contains</strong> metodu yerine örneğin <strong>StartsWith</strong> fonksiyonu kullanılırsa ne olacağına bakılmalıdır. Bu amaçla <strong> LINQ to SQL</strong> ifadesini aşağıdaki gibi değiştirdiğimizi düşünelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var allProducts = from prd in adw.Products
where prd.Class == null && prd.ProductNumber.StartsWith("PA")
select prd;</pre>
<p>Bu durumda SQL tarafına gönderilen sorgu cümlesinin içeriğine bakıldığında <strong>LIKE</strong> anahtar kelimesine atanan parametrenin <strong>PA%</strong> şeklinde olduğu görülmektedir. Dolayısıyla beklenildiği gibi <strong>PA</strong> hecesi ile başlayanların tedarik edilmesi için sorguda gerekli düzenleme yapılmıştır.</p>
<p>Bir önceki <strong>LINQ to SQL</strong> ifadesinde, SQL sunucusu üzerinde çalışan sorguya bakıldığında <strong> Product</strong> tablosundaki tüm alanların çekildiği görülmektedir. Oysaki çoğu durumda elde edilip veri kümesi <strong>Entity</strong> üzerine alındığında yanlızca bir kaç alan üzerinde işlem yapılmaktadır. Söz gelimi elde edilen listenin bir <strong>GridView</strong> üzerinde gösterilmesi istendiğinde tüm alanlar yerine gerekli olanların gösterilmesi tercih edilir. İşte bu noktada <strong>isimsiz tiplerin(Anonymous Types)</strong> faydası ortaya çıkmaktadır. Buna göre aşağıdaki LINQ to SQL ifadesini ele alalım.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var allProducts = from prd in adw.Products
where prd.Class == null && prd.ProductNumber.Contains("PA")
select new
{
prd.Name
, prd.ProductNumber
};
foreach (var p in allProducts)
{
Console.WriteLine(p.ToString());
}</pre>
<p>Bu örnek kod parçasında <strong>PA</strong> hecesini içeren ve <strong>Class</strong> alanının değeri <strong>null </strong>olan <strong> Product</strong> tiplerindeki <strong>Name</strong> ve <strong>ProductNumber</strong> özellikleri kullanılarak yeni bir tip elde edilmektedir. Burada ihtiyaçlar dahilinde <strong>isimsiz tipin(Anonymous Type)</strong> hangi özellikleri(Properties) içereceği belirlenebilir. Bu örnek kod parçasının çalışma zamanında üreteceği çıktı aşağıdaki ekran görüntüsündekine benzer olacaktır.</p>
<p><img src="/makale/images/mk236_6.gif" alt="" width="494" height="131" border="0" /></p>
<p>SQL sunucusu tarafına gönderilen sorgu cümlesinin içeriği ise aşağıdaki gibidir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'SELECT [t0].[Name], [t0].[ProductNumber]
FROM [Production].[Product] AS [t0]
WHERE
([t0].[Class] IS NULL) AND ([t0].[ProductNumber] LIKE @p0)'
,N'@p0 nvarchar(4)',@p0=N'%PA%'</pre>
<p>Görüldüğü gibi sadece istenen alanların çekilmesi sağlanmaktadır. Buda isimsiz tiplerin <strong>LINQ to SQL</strong> tarafında oldukça önemli bir rol oynadığını göstermektedir.</p>
<p><strong>LINQ to SQL</strong> tarafında kullanılan ilginç fonksiyonelliklerden ikiside <strong>Skip</strong> ve <strong>Take</strong> metodlarıdır. Skip metodu parametre olarak verilen değer kadar atlanılmasını, Take metodu ise atlanılan satırdan itibaren kaç satır alınacağını belirtmektedir. Buda basit olarak bir sayfalamanın(Paging) yapılabilmesine olanak sağlamaktadır. Çok doğal olarak metodlar yardımıyla programatik ortamda kolayca uygulanabilen bu tekniğin SQL tarafına aktarılmasında <strong>SQL 2005</strong> ile birlikte gelen <strong>Row_Number</strong> fonksiyonunun önemli bir rolü vardır. Bu metodları daha iyi analiz etmek için aşağıdaki kod parçasını ele aldığımızı düşünelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var tenCtg = (from cat in adw.ProductSubcategories select cat)
.Skip<ProductSubcategory>(5)
.Take<ProductSubcategory>(10);
foreach (ProductSubcategory c in tenCtg)
{
Console.WriteLine("{0} -> {1} ", c.ProductSubcategoryID.ToString(), c.Name);
}</pre>
<p>Yukarıdaki kod parçasında yer alan <strong> LINQ to SQL</strong> ifadesine göre, <strong>ProductSubCategories</strong> koleksiyonu üzerinden ilk satır atlanarak 6ncı satırdan itibaren 10 satırlık bir <strong> ProductSubCategory</strong> nesne topluluğunun çekilmesi amaçlanmaktadır. Söz konusu kod parçasının çalıştırılmasının sonucu oluşan program çıktısına ait ekran görüntüsü aşağıdaki gibidir.</p>
<p><img src="/makale/images/mk236_7.gif" alt="" width="290" height="170" border="0" /></p>
<p>Örnek kod parçasında kullanılan <strong>LINQ to SQL</strong> ifadesi için <strong>SQL</strong> sunucusu üzerine gönderilen sorgu cümlesi ise aşağıdaki gibi olacaktır.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'SELECT [t1].[ProductSubcategoryID], [t1].[ProductCategoryID], [t1].[Name], [t1].[rowguid], [t1].[ModifiedDate]
FROM (
SELECT ROW_NUMBER() OVER
(ORDER BY
[t0].[ProductSubcategoryID], [t0].[ProductCategoryID],
[t0].[Name], [t0].[rowguid]
, [t0].[ModifiedDate])
AS [ROW_NUMBER], [t0].[ProductSubcategoryID], [t0].[ProductCategoryID], [t0].[Name], [t0].[rowguid], [t0].[ModifiedDate]
FROM [Production].[ProductSubcategory] AS [t0]
) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p0 + @p1
ORDER BY [t1].[ROW_NUMBER]',N'@p0 int,@p1 int',@p0=5,@p1=10</pre>
<p>Dikkat edileceği üzere <strong>Row_Number</strong> fonksiyonu burada önemli bir rolü üstlenmektedir. Şimdi son kod parçasında aşağıdaki gibi bir ekleme daha yaptığımızı düşünelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var tenCtg = (from cat in adw.ProductSubcategories select cat)
.Skip<ProductSubcategory>(5)
.Take<ProductSubcategory>(10);
foreach (ProductSubcategory c in tenCtg)
{
Console.WriteLine("{0} -> {1}({2}) ",c.ProductSubcategoryID.ToString(),c.Name,c.Products.Count().ToString());
}</pre>
<p>Yapılan değişikliğe göre, 5nci satırdan itibaren alınan 10 <strong>ProductSubCategory</strong> nesnesinin her biri için <strong> Products</strong> özelliğinden yola çıkılarak <strong>Count</strong> değerleride hesaplanmaktadır. Bir başka deyişle her bir alt kategorideki ürünlerin toplam sayılarıda elde edilmektedir. Bu kod parçasının <strong>çalışma zamanı(run time)</strong> görüntüsü ise aşağıdaki gibidir.</p>
<p><img src="/makale/images/mk236_8.gif" alt="" width="289" height="169" border="0" /></p>
<p><strong>ProductSubCategory</strong> ve <strong>Product</strong> sınıfları arasında <strong>bire çok(one to many)</strong> bir ilişki mevcuttur. Bu ilişki programatik taraftada ilgili <strong>varlık sınıflarına(Entity Class) </strong> yansıtılmaktadır. Bu nedenle bir ProductSubCategory nesne örneği üzerinden Products özelliği ile bağlı olunan Product nesne topluluğuna geçiş yapmak son derece kolaydır. Bu da gönül rahatlığı ile <strong>Count</strong> gibi metodları kullanıp istediğimiz tarzda sonuçları alabilmemizi olanaklı kılmaktadır. Ne varki <strong>Count</strong> çağrısı <strong>for</strong> döngüsü içerisinde, bir önceki sorgudan elde edilen her bir ProductSubCategory nesne örneği için ayrı ayrı yapılmaktadır. Bunun <strong>SQL</strong> sunucusu üzerinde oluşturacağı sonuç ise şudur; elde edilen her bir ProductSubCategory için, buna bağlı toplam ürün sayısını döndüren bir sorgu cümlesi çalışmaktadır. Aşağıdaki ekran görüntüsünde bu durumun bir kısmı ifade edilmektedir.</p>
<p><img src="/makale/images/mk236_9.gif" alt="" width="566" height="674" border="0" /></p>
<p>Sorgu ifadelerine dikkat edilecek olursa <strong>Count</strong> metodu çağırıldığında aslında SQL tarafında bir <strong> Count</strong> hesabı <span style="text-decoration: underline;">yapılmamaktadır. <strong>Products</strong> özelliğine geçildiğinden, o anki <strong>ProductSubCategoryID</strong> değerine bağlı Product satırları, programatik taraftaki nesne topluluğuna yüklenmektedir. Bu nesneler yüklendikten sonra bildiğimiz koleksiyonlara ait olan <strong>Count</strong> metodu çalışmakta ve toplam ürün sayıları bu şekilde elde edilmektedir. Hiç bir durumda bu tarz bir yol ile toplam sayıların elde edilmesi tercih edilmemelidir. Görüldüğü gibi gayet masumane olan ama çok işe yaradığı düşünülen basit bir kod parçası arka tarafta son derece fazla sayıda ve yoğun sorgu cümlelerinin çalışmasına neden olmuştur.</span></p>
<p>SQL tarafında birden fazla tablo üzerinde bir arada işlem yapılması gerektiği durumlarda çoğunlukla <strong> join</strong> yapılarından yararlanılmaktadır. Aynı yapı bildiğiniz gibi <strong> LINQ</strong> ile nesneler üzerindede gerçekleştirilebilmektedir. Sıradaki örnekte LINQ to SQL için <strong>join</strong> kullanımına bakılmaktadır. Bu amaçla aşağıdaki gibi bir kod parçası geliştirdiğimizi düşünelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var allList = from pc in adw.ProductCategories
join psc in adw.ProductSubcategories
on pc.ProductCategoryID equals psc.ProductCategoryID
join p in adw.Products
on psc.ProductSubcategoryID equals p.ProductSubcategoryID
where p.Class == null && p.ListPrice > 100
select new
{
CategoryName = pc.Name
, SubCategoryName = psc.Name
, ProductNumber = p.ProductNumber
, ProductName = p.Name
};
foreach (var prd in allList)
{
Console.WriteLine(prd.ToString());
}</pre>
<p>Buradaki kod parçasına göre <strong> ProductCategories</strong>, <strong>ProductSubCategories</strong> ve <strong>Products</strong> özelliklerine bağlı generic <strong>Table<T></strong> koleksiyonları join anahtar kelimesi yardımıyla anahtar özellikler üzerinden birleştirilmekte ve yeni bir <strong>isimsiz tipe(Anonymous Type)</strong> ait nesne topluluğu elde edilmektedir. Bu nesne topluluğu elde edilirken <strong>Class</strong> değeri <strong> null</strong> olan ve ürünlerin <strong>ListPrice</strong> değeri 100' den büyük olanların elde edilmesi sağlanmaktadır. Buna göre uygulamanın ekran çıktısı aşağıdaki gibi olacaktır.</p>
<p><img src="/makale/images/mk236_10.gif" alt="" width="638" height="245" border="0" /></p>
<p>Söz konusu LINQ to SQL ifadesinin SQL tarafındaki karşılığı ise aşağıdaki gibidir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'SELECT [t0].[Name] AS [CategoryName], [t1].[Name] AS [SubCategoryName], [t2].[ProductNumber], [t2].[Name] AS [ProductName]
FROM [Production].[ProductCategory] AS [t0]
INNER JOIN [Production].[ProductSubcategory] AS [t1] ON [t0].[ProductCategoryID] = [t1].[ProductCategoryID]
INNER JOIN [Production].[Product] AS [t2] ON ([t1].[ProductSubcategoryID]) = [t2].[ProductSubcategoryID]
WHERE ([t2].[Class] IS NULL) AND ([t2].[ListPrice] > @p0)',N'@p0 decimal(33,4)',@p0=100.0000</pre>
<p>Görüldüğü gibi join kelimeleri SQL tarafında standart <strong>inner join</strong> muamelesi görmektedir.</p>
<p>Sıradaki <strong>LINQ to SQL</strong> ifadesinde <strong> DateTime</strong> <strong>yapısının(struct) </strong>parçalarından yararlanılmakta olup, bu ayrıştırmanın SQL tarafına nasıl yansıtıldığı incelenmeye çalışılmaktadır. Bu amaçla uygulamaya aşağıdaki kod parçasını eklediğimizi düşünelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"> var result = from p in adw.Products
where p.SellStartDate.Month >= 6 && p.SellStartDate.Month <= 12
select new
{
p.ProductNumber
, p.Name
, p.ListPrice
, p.SellStartDate
, p.SellEndDate
};
foreach (var p in result)
{
Console.WriteLine(p.ProductNumber + " " + p.SellStartDate.Month.ToString() + " ");
}</pre>
<p>Yukarıdaki kod parçasında yer alan <strong> LINQ</strong> ifadesinde, her bir <strong>Product</strong> nesne örneğinin <strong> SellStartDate</strong> özellikleri üzerinden hareket edilerek <strong>Month</strong> değerlerine bakılmakta ve 6ncı ila 12nci ay arasında olanlar değerlendirilerek yeni bir isimsiz tip(Anonymous Type) içerisinde toplanmaktadır. Örneğe ait çalışma zamanı ekran çıktısı aşağıdaki gibidir.</p>
<p><img src="/makale/images/mk236_11.gif" alt="" width="236" height="340" border="0" /></p>
<p>Burada merak edilen konu, <strong>Month</strong> değerlerinin <strong>SQL</strong> tarafında nasıl ele alınacağıdır. Bu amaçla örnek çalıştırıldıktan sonra <strong>SQL Server Profiler </strong>aracına bakılırsa, <strong>DatePart</strong> <strong>SQL</strong> fonksiyonunun kullanıldığı açık bir şekilde görülebilir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'SELECT [t0].[ProductNumber], [t0].[Name], [t0].[ListPrice], [t0].[SellStartDate], [t0].[SellEndDate]
FROM [Production].[Product] AS [t0]
WHERE
(DATEPART(Month, [t0].[SellStartDate]) >= @p0)
AND (DATEPART(Month, [t0].[SellStartDate]) <= @p1)'
,N'@p0 int,@p1 int',@p0=6,@p1=12</pre>
<p>Böylece <strong>SellStartDate</strong> alanlarının içeriği <strong>DatePart</strong> fonksiyonu kullanılaraktan ayrıştırılmakta ve elde edilen <strong>Month</strong> kısmına görede değer aralığı kontrolü yapılmaktadır.</p>
<p>Bazı durumlarda sorgulanan verinin <strong>string</strong> bazlı olması halinde karakter tabanlı kontroller yapılmak istenebilir. Söz gelimi B harfi ile başlayan ürünlerin elde edilmesi gibi. Bu gibi durumlarda <strong>string</strong> bazlı verilerin karakter katarı olduğu programatik tarafta ele alınması gereken bir durumdur. Aşağıdaki kod parçasında hem bu durum ele alınmakta hemde <strong>First</strong> metodunun kullanımı incelenmektedir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">Product result = (from p in adw.Products select p)
.First<Product>(prd => prd.Name[0] == 'C');
Console.WriteLine(result.Name + " " + result.ListPrice);</pre>
<p>Buradaki ifadeye göre, her bir <strong> Product</strong> nesne örneğinin <strong>Name</strong> özelliklerinin ilk harflerine bakılmaktadır. İlk harfi C olan ürünlerden ise sadece ilki First metodu yardımıyla elde edilmektedir. Bu işlevselliği gerçekleştirmek için <strong> First</strong> metodu içerisinde <strong>lambda(=>) </strong>operatörü kullanılmaktadır. Lambda operatörü sayesinde eşitliğin sol tarafından sağ tarafına o anki Product nesne örneği geçirilmektedir. Eşitliğin sağ tarafında ise o anki Product nesne örneğinin <strong>Name</strong> özelliğinin ilk harfine bakılmaktadır. Eğer ilk harf <strong>C</strong> ise o anda üzerinde durulan <strong>Product</strong> nesne örneği eşitliğin sağından soluna doğru geri döndürülmektedir. Örneğin çalışma zamanındaki ekran çıktısı aşağıdaki gibi olacaktır.</p>
<p><img src="/makale/images/mk236_13.gif" alt="" width="297" height="64" border="0" /></p>
<p>Söz konusu <strong>LINQ to SQL</strong> ifadesinin SQL tarafına aktarılan sorgu cümlesi ise aşağıdaki gibidir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'SELECT TOP (1)
[t0].[ProductID], [t0].[Name], [t0].[ProductNumber]
, [t0].[MakeFlag], [t0].[FinishedGoodsFlag], [t0].[Color]
, [t0].[SafetyStockLevel], [t0].[ReorderPoint], [t0].[StandardCost]
, [t0].[ListPrice], [t0].[Size], [t0].[SizeUnitMeasureCode]
, [t0].[WeightUnitMeasureCode], [t0].[Weight], [t0].[DaysToManufacture]
, [t0].[ProductLine], [t0].[Class], [t0].[Style], [t0].[ProductSubcategoryID]
, [t0].[ProductModelID], [t0].[SellStartDate], [t0].[SellEndDate]
, [t0].[DiscontinuedDate], [t0].[rowguid], [t0].[ModifiedDate]
FROM [Production].[Product] AS [t0]
WHERE
UNICODE(CONVERT(NChar(1),SUBSTRING([t0].[Name], @p0 + 1, 1))) = @p1',N'@p0 int,@p1 int',@p0=0,@p1=67</pre>
<p>Herşeyden önce <strong>First</strong> metodunun tam karşılığı olarak <strong>TOP (1) </strong>söz dizimi kullanılmaktadır. Diğer taraftan programatik ortamda <strong>[] indeksleyici</strong> operatörünü kullanarak <strong>string</strong> veri tipinin ilk karakterine geçmemiz ve C harfini kontrol etmemizin karşılığı <strong>Unicode</strong>, <strong>Convert</strong>, <strong>SubString SQL</strong> fonksiyonları olmuştur. Burada 67 değerinin C harfine karşılık geldiğini hatırlayalım. Elbette çekilen veri bir <strong>Product</strong> tipi olduğundan, <strong>Product</strong> tablsoundaki tüm alanların <strong>Select</strong> ifadesine alındığı görülmektedir. Daha öncedende belirtildiği gibi, sadece gerekli alanların çekilmesi adına kod tarafında <strong>isimsiz metod(Anonymous Method) </strong>kullanımına gidilebilir.</p>
<p>LINQ tarafında <strong>çoklu seçimlerde(Select Many) </strong>yapılabilmektedir. Bu tarz bir kullanıma örnek olarak aşağıdaki kod parçası ele alınabilir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var result = from p in adw.SalesPersons
where p.Bonus >= 1000
from h in p.SalesOrderHeaders
where h.TerritoryID == 1
select new
{
p.SalesPersonID
, p.Bonus
, h.SubTotal
, h.AccountNumber
};
foreach (var r in result)
{
Console.WriteLine(r.ToString());
}</pre>
<p>Buradaki sorgu ifadesine göre, <strong> SalesPersons</strong> koleksiyonunda tutulan <strong>SalesPerson</strong> nesne örneklerinden <strong>Bonus</strong> özelliklerinin değeri 1000' in üzerinde olanlar alınmaktadır. Sonrasında ise elde edilen kümedeki her bir <strong> SalesOrder</strong> üzerinden <strong>SalesOrderHeaders</strong> koleksiyonuna gidilmekte ve bölge değeri 1 olanlar çekilmektedir. Bir başka deyişle Bonus' u 1000' in üzerinde ve sipariş kalemleri 1 numaralı bölgeye doğru yapılmış olan satış personelinin elde edilmesi söz konusudur. Elde edilen veri kümesi değerlendirilerek yeni bir <strong>isimsiz tip(Anonymous Type)</strong> içerisinde birleştirilmeleri sağlanmaktadır. Örnek kodun çalışma zamanındaki çıktısı aşağıdaki gibi olacaktır.</p>
<p><img src="/makale/images/mk236_14.gif" alt="" width="637" height="224" border="0" /></p>
<p>Şu aşamada bizim ilgilendiğimiz kısım <strong>SQL</strong> tarafına gönderilen sorgu cümlesidir. Bu cümlede aşağıdaki şekildedir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'SELECT [t0].[SalesPersonID], [t0].[Bonus], [t1].[SubTotal], [t1].[AccountNumber]
FROM [Sales].[SalesPerson] AS [t0], [Sales].[SalesOrderHeader] AS [t1]
WHERE ([t1].[TerritoryID] = @p0) AND ([t0].[Bonus] >= @p1)
AND ([t1].[SalesPersonID] = [t0].[SalesPersonID])'
,N'@p0 int,@p1 decimal(33,4)',@p0=1,@p1=1000.0000</pre>
<p>Burada <strong>From</strong> kelimesinden sonraki kısma bakıldığında <strong>SalesPerson</strong> ve <strong>SalesOrderHeader</strong> tablolarının birlikte ele alındıkları görülmektedir.</p>
<p>Gelelim gruplama fonksiyonelliklerinin <strong>SQL</strong> tarafına nasıl yansıtıldığında. Bu amaçla aşağıdaki örnek kod parçasını göz önüne alıyor olacağız.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var result = from p in adw.Products
where p.Class != null
group p by p.Class into g
select new
{
ClassName = g.Key
,TotalListPrice = g.Sum<Product>(p => p.ListPrice)
};
foreach (var r in result)
{
Console.WriteLine("{0} : {1}", r.ClassName, r.TotalListPrice);
}</pre>
<p>Örnekteki ifadede, <strong>Products</strong> koleksiyonundaki her bir <strong>Product</strong> nesnesinin <strong>Class</strong> özelliklerine göre gruplara ayrılması ve her bir gruba ait <strong>ListPrice</strong> özelliklerinin toplam değerlerinin bulunması sağlanmaktadır. Bir başka deyişle sınıfları olan ürünlerin sınıflara göre gruplandıklarında, toplam liste fiyatı değerlerinin ne olduğu elde edilmektedir. Bu kod parçasının icra edilmesi halinde, çalışma zamanında aşağıdakine benzer sonuç ortaya çıkmaktadır.</p>
<p><img src="/makale/images/mk236_15.gif" alt="" width="289" height="84" border="0" /></p>
<p>Görüldüğü gibi ürünler sınıflara göre gruplanmış ve toplam ürün fiyatlarının değerleri elde edilmiştir. Burada çalışan <strong>LINQ</strong> <strong>to SQL</strong> ifadesinin <strong>SQL</strong> tarafına gönderilen karşılığı ise aşağıdaki gibi olacaktır.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">SELECT SUM([t0].[ListPrice]) AS [TotalListPrice], [t0].[Class] AS [ClassName]
FROM [Production].[Product] AS [t0]
WHERE [t0].[Class] IS NOT NULL
GROUP BY [t0].[Class]</pre>
<p>Aslında üretilen <strong>SQL</strong> cümlesi tam olarak düşündüğümüz şekildedir. Bununla birlikte dikkat edilmesi gereken bir husus vardır. Buda <strong>LINQ</strong> sorgusundaki <strong>where</strong> kelimesinin kullanıldığı yerdir. Örnekte, <strong>where</strong> ifadesi ile seçilen küme üzerinde gruplama yapılmaktadır. Bu nedenle <strong>group by</strong> kelimesinden önce where kullanılmaktadır. Ancak aynı ifade aşağıdaki haliylede geliştirilebilir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var result = from p in adw.Products
group p by p.Class into g
where g.Key!=null
select new
{
ClassName = g.Key
,TotalListPrice = g.Sum<Product>(p => p.ListPrice)
};
foreach (var r in result)
{
Console.WriteLine("{0} : {1}", r.ClassName, r.TotalListPrice);
}</pre>
<p>Bu sefer gruplanan nesneye ait <strong>Key</strong> özelliğinin <strong>null</strong> olup olmadığına bakılmaktadır. Kod bu haliyle çalıştırıldığında da bir önceki ile aynı sonuçların elde edildiği görülebilir. Ne varki <strong>SQL</strong> tarafına gönderilen ifadeye bakıldığında aşağıdaki sonuçlar ortaya çıkmaktadır.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">SELECT [t1].[Class] AS [ClassName], [t1].[value] AS [TotalListPrice]
FROM (
SELECT SUM([t0].[ListPrice]) AS [value], [t0].[Class]
FROM [Production].[Product] AS [t0]
GROUP BY [t0].[Class]
) AS [t1]
WHERE [t1].[Class] IS NOT NULL</pre>
<p>Sonuç bir öncekinden oldukça farklıdır. Bu kez devreye ek bir alt sorgu cümlesi daha girmektedir. Önce sınıflara göre gruplanmış ürünlerin <strong>ListPrice</strong> değelerinin toplamları ve sınıf adlarının olduğu küme elde edilmektedir. Sonrasında ise bu küme üzerinden <strong>Class</strong> değerleri <strong>null</strong> olmayanlar çekilmektedir. Bu noktada <strong>where</strong> kelimesinin kod tarafından yerinin değiştirilmesinin önemli olup olmadığına karar vermek gerekebilir. Ancak geliştirilen örneğe ait oluşturulan sorguların <strong> icra planlarına(Execution Plan)</strong> bakıldığında bir fark olmadığı açıkça görülmektedir.</p>
<p><img src="/makale/images/mk236_16.gif" alt="" width="638" height="593" border="0" /></p>
<p><strong>LINQ</strong> tarafında yer alan enteresan metodlardan biriside <strong>Except</strong> metodudur. Bu metoddan yararlanılarak belirli bir şartın dışında kalan nesnel kümelerin elde edilmesi sağlanabilir. Örnek olarak aşağıdaki gibi bir kod parçası geliştirdiğimizi düşünelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var result = (from c in north.Customers select c.City)
.Except(from s in north.Suppliers select s.City);
foreach (var r in result)
{
Console.WriteLine(r);
}</pre>
<p>Bu örnekte <strong>NorthwindDataContext</strong> kullanılmaktadır. Buna göre <strong>LINQ</strong> ifadesinde ilk parantez içerisinde kalan kısımda <strong>Customers</strong> koleksiyonunda duran <strong> Customer</strong> nesne örneklerinden <strong>City</strong> özellikleri çekilmektedir. <strong>Except</strong> metodunda yazılan ifadede <strong>Suppliers</strong> tablosunda yer alan <strong>Supplier</strong> nesne örneklerinden <strong>City</strong> özelliklerini çekmektedir. Her iki küme bir arada düşünüldüğünde ortaya çıkan sonuç şudur; Customer nesne örneklerinde olup, Supplier nesne örneklerinde bulunmayan <strong>City</strong> özellikleri elde edilmektedir. Daha düzgün bir ifadeyle, bir başka deyişle <strong>SQL</strong>' ce düşünüşdüğünde, müşterilerin yaşayıpta tedarikçilerinin bulunmadığı şehir adlarının elde edildiğini söyleyebiliriz. Programın çalışma zamanındaki çıktısı aşağıdaki gibidir.</p>
<p><img src="/makale/images/mk236_17.gif" alt="" width="208" height="279" border="0" /></p>
<p>Bu tarz bir ihtiyacı SQL tarafında karşılamak için <strong>Not In</strong> kullanımı tercih edilebilir. <strong>LINQ</strong> tarafında metod bazlı yazılan bu örnek ise, <strong>SQL</strong> tarafına aşağıdaki şekilde aktarılmaktadır.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">SELECT DISTINCT [t0].[City]
FROM [dbo].[Customers] AS [t0]
WHERE
NOT (EXISTS
(
SELECT NULL AS [EMPTY]
FROM [dbo].[Suppliers] AS [t1]
WHERE (([t0].[City] IS NULL)
AND ([t1].[City] IS NULL))
OR (([t0].[City] IS NOT NULL)
AND ([t1].[City] IS NOT NULL)
AND ([t0].[City] = [t1].[City]))
)
)</pre>
<p>Burada önemli olan <strong>Exists</strong> <strong>SQL</strong> fonksiyonu ile gereken işlevselliğin sağlanmış olmasıdır. <strong>Not</strong> konulmasının sebebi, <strong>Exists</strong> ile belirtilen alt sorgudaki koşula uyanların dışarıda bırakılmasını sağlamaktır. Nitekim t0 ve t1 tablolarındaki City değerlerine bakılarak eşit olanların elde edilmesi sağlanırken <strong>Except</strong> metodu kullanılması nedeniyle bunların dışarıda tutulmasını ancak <strong>Not</strong> anahtar kelimesi sağlayabilmektedir. Ayrıca hem <strong>Suppliers</strong> hemde <strong>Customers</strong> tablosundaki <strong>City</strong> alanları için detaylı bir<strong> Null</strong> kontrolü yapılmaktadır.</p>
<p>Makalemize yine enteresan <strong>LINQ</strong> fonksiyonları ile devam edelim. Bu kez <strong>Any</strong> ve <strong>All</strong> isimli metodları incelemeye çalışıyor olacağız. Bu amaçla ilk olarak Any metodunun kullanımına kısaca bakılım.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var result = from p in adw.SalesPersons
where p.SalesOrderHeaders.Any(soh => soh.SubTotal >= 224356)
select p;
foreach (SalesPerson person in result)
{
Console.WriteLine("Person Id: " + person.SalesPersonID + " Bonus: " + person.Bonus + " Sales Last Year : " + person.SalesLastYear);
foreach (SalesOrderHeader header in person.SalesOrderHeaders)
Console.WriteLine("\t" + header.AccountNumber + " Sub Total: " + header.SubTotal);
}</pre>
<p><strong>LINQ to SQL</strong> ifadesinde <strong>SalesPersons</strong> koleksiyonundaki her bir <strong>SalesPerson</strong> çekilmektedir. Bunlara bağlı olan <strong>SalesOrderHeaders</strong> koleksiyonundaki <strong>SalesOrderHeader</strong> nesne örneklerinin ise <strong>SubTotal</strong> değerlerine bakılarak seçim işlemi koşullandırılmaktadır. Any metodunun buradaki görevi ise şudur; <strong>SubTotal</strong> değerlerinden herhangibiri 224356' nın üzerinde olan satırlar elde edilebilmektedir. Yani, satış personelinin siparişlerine ait SubTotal değerlerinden herhangibiri <strong>224356</strong>' nın üzerinde olanların elde edilmesi sağlanmaktadır. Örnek kod parçasının çalışma zamanındaki çıktısı aşağıdaki gibi olacaktır.</p>
<p><img src="/makale/images/mk236_18.gif" alt="" width="508" height="340" border="0" /></p>
<p>Bu tarz bir işleyiş için <strong>SQL</strong> tarafı göz önüne alındığında ortaya karmaşık bir sorgu çıkacağı düşünülebilir. Nitekim <strong>LINQ</strong> to SQL ifadesinin SQL tarafındaki karşılığı aşağıdaki gibidir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'
SELECT
[t0].[SalesPersonID], [t0].[TerritoryID], [t0].[SalesQuota]
, [t0].[Bonus], [t0].[CommissionPct], [t0].[SalesYTD]
, [t0].[SalesLastYear], [t0].[rowguid], [t0].[ModifiedDate]
FROM [Sales].[SalesPerson] AS [t0]
WHERE EXISTS(
SELECT NULL AS [EMPTY]
FROM [Sales].[SalesOrderHeader] AS [t1]
WHERE ([t1].[SubTotal] >= @p0) AND ([t1].[SalesPersonID] = [t0].[SalesPersonID])
)'
,N'@p0 decimal(33,4)',@p0=224356.0000</pre>
<p>Dikkat edileceği üzere <strong>Exists</strong> anahtar kelimesi kullanılarak <strong>SubTotal</strong> değeri ele alınmakta ve buna uyanların <strong>SalesPerson</strong> tablosundan çekilmesi sağlanmaktadır. Gelelim <strong>All</strong> metoduna. Bu sefer <strong>Any</strong>' den farklı olarak bağlı olunan kümedeki her bir eleman için belirtilen koşulun sağlanmış olma şartı aranmaktadır. Bunu daha net kavrayabilmek için örneğimizi aşağıdaki gibi değiştirelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var result = from p in adw.SalesPersons
where p.SalesOrderHeaders.All(soh => soh.SubTotal >= 80)
select p;
foreach (SalesPerson person in result)
{
Console.WriteLine("Person Id: " + person.SalesPersonID + " Bonus: " + person.Bonus + " Sales Last Year : " + person.SalesLastYear);
foreach (SalesOrderHeader header in person.SalesOrderHeaders)
Console.WriteLine("\t" + header.AccountNumber + " Sub Total: " + header.SubTotal);
}</pre>
<p>Bu kodun çalışma zamanı çıktısı ise aşağıdaki gibi olacaktır.</p>
<p><img src="/makale/images/mk236_19.gif" alt="" width="508" height="340" border="0" /></p>
<p>Bu sefer bir <strong>SalesOrder</strong>' ın bağlı olduğu <strong>SalesOrderHeaders</strong> koleksiyonundaki her bir <strong> SalesOrderHeader</strong> nesne örneğinin <strong>SubTotal</strong> değerlerinin her biri 80' in üzerinde olanların elde edilmesi sağlanmaktadır. Bir başka deyişle bir <strong>SalesOrder</strong> üzerinden ulaşılan nesne topluluğunda n tane <strong>SalesOrderHeader</strong> olduğu düşünülecek olursa, bunların her birine ait <strong>SubTotal</strong> özelliklerinin değerlerinin 80 ve üzerinde olma şartı konulmaktadır. Söz konusu <strong>All</strong> metodu için SQL tarafında üretilen çıktı ise aşağıdaki gibidir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'
SELECT
[t0].[SalesPersonID], [t0].[TerritoryID], [t0].[SalesQuota]
, [t0].[Bonus], [t0].[CommissionPct], [t0].[SalesYTD]
, [t0].[SalesLastYear], [t0].[rowguid], [t0].[ModifiedDate]
FROM [Sales].[SalesPerson] AS [t0]
WHERE NOT (
EXISTS(
SELECT NULL AS [EMPTY]
FROM [Sales].[SalesOrderHeader] AS [t1]
WHERE ((
(CASE
WHEN [t1].[SubTotal] >= @p0 THEN 1 ELSE 0
END)) = 0)
AND ([t1].[SalesPersonID] = [t0].[SalesPersonID])
))'
,N'@p0 decimal(33,4)',@p0=80.0000</pre>
<p>Bu kez koşulun kontrolü için <strong>Case</strong> ifadesinden yararlanılmakta ve <strong>SubTotal</strong> 80' üzerinde ise 1, değilse 0 değeri <strong>Where</strong> ifadesine katılarak 0 olanların çekilmesi sağlanmaktadır. Yanlız burada yine Not Exist kullanıldığında dikkat etmekte yarar vardır. Buna görede bir <strong>Where</strong> ifadesinin <strong>SalesPerson</strong> tablosu için üretilmesi sağlanmaktadır.</p>
<p>Örneklerimize <strong>Concat</strong> metodu ile devam edelim. Concat metodunu daha çok iki farklı sonuç kümesindeki belirli özelliklerin bir arada ele alınmasını istediğimiz durumlarda göz önüne alabiliriz. Bu bir anlamda iki <strong>string</strong>' in birleştirilemesine benzer bir durumdur. Tabi şu anda söz konusu olan string değil <strong>IEnumerable</strong> gibi referanslardır. <strong>Concat</strong> metodunun SQL tarafında ürettiği çıktıya bakmak için aşağıdaki örnek kod parçasını geliştirdiğimizi düşünelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var result = (from cust in north.Customers select new { cust.Country, cust.City })
.Concat(from supl in north.Suppliers select new { supl.Country, supl.City });
foreach (var r in result)
{
Console.WriteLine(r.Country + ":" + r.City);
}</pre>
<p>Bu örnekte <strong>NorthwindDataContext</strong> tipi kullanılmakta olup <strong>Customers</strong> ve <strong>Suppliers</strong> koleksiyonlarındaki nesnelerde <strong>Country</strong> ve <strong>City</strong> değerleri birleştirilip çekilmektedir. Sonuçta kodun çalışma zamanı çıktısı aşağıdaki gibi olacaktır.</p>
<p><strong> <img src="/makale/images/mk236_20.gif" alt="" width="213" height="340" border="0" /></strong></p>
<p>Elbetteki SQL tarafına bakıldığında <strong> Concat</strong> metodunun aşağıdakine benzer bir dönüşüme uğradığı görülmektedir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">SELECT [t2].[Country], [t2].[City]
FROM (
SELECT [t0].[Country], [t0].[City]
FROM [dbo].[Customers] AS [t0]
UNION ALL
SELECT [t1].[Country], [t1].[City]
FROM [dbo].[Suppliers] AS [t1]
) AS [t2]</pre>
<p>Açıkça<strong> Union All </strong>kullanılaraktan iki <strong>Select</strong> ifadesinin birleştirildiği ve elde edilen küme üzerinden <strong>Country</strong> ile <strong>City</strong> alanlarına ait değerlerin çekildiği söylenebilir. Örnekte dikkati çeken noktalardan biriside tekrarlı alanların olmasıdır. Örneğin ekran çıktısının üst taraflarına bakıldığında iki adet Mexico kentinin olduğu London' un iki kere geçtiği rahat bir şekilde görülebilir. Çok doğal olarak tekrarsız bir listenin elde edilmesi istendiğinde kod tarafında <strong>Distinct</strong> metodunun kullanılıyor olması yeterli olacaktır. Yani kod parçasında aşağıdaki değişikliğin yapılması yeterlidir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var result = (from cust in north.Customers select new { cust.Country, cust.City }).Concat(from supl in north.Suppliers select new { supl.Country, supl.City }).Distinct();</pre>
<p>Bu durumda programın çıktısı aşağıdaki gibi olacaktır.</p>
<p><strong> <img src="/makale/images/mk236_21.gif" alt="" width="235" height="208" border="0" /></strong></p>
<p>Diğer taraftan <strong>Distinct</strong> metodunun kullanılması sonrasında <strong>SQL</strong> tarafına gönderilen sorgu cümlesinde ise <strong>Distinct</strong> anahtar kelimesinin kullanıldığıda aşikardır.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">SELECT DISTINCT [t3].[Country], [t3].[City]
FROM (
SELECT [t2].[Country], [t2].[City]
FROM (
SELECT [t0].[Country], [t0].[City]
FROM [dbo].[Customers] AS [t0]
UNION ALL
SELECT [t1].[Country], [t1].[City]
FROM [dbo].[Suppliers] AS [t1]
) AS [t2]
) AS [t3]</pre>
<p>Ancak burada bir öncekinden farklı bir sorgunun oluştuğuda gözlerden kaçmamalıdır. Bu kez iç içe alınmış <strong> Select</strong> sorgusu söz konusudur. Oysaki en dışta yer alan <strong>Select</strong> kullanımına gerek yoktur. Çünkü <strong>Distinct</strong> anahtar kelimesi içerideki sorgu cümlesine eklenerekte aynı sonuçların alınması sağlanabilir. Ne varki sorgu cümlesini bu şekilde oluşturup SQL tarafına gönderen <strong>LINQ to SQL</strong> mimarisidir.</p>
<p>Yine ilginç bir LINQ metodu ve SQL karşılığı ile devam edelim. Bu kez iki farklı veri kümesinin kesişimlerinin elde edilmesinde kullanılabilen <strong>Intersect</strong> metodu üzerinde duracağız. Bu metodun analizi için aşağıdaki gibi bir kod parçası geliştirdiğimizi düşünelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var result = (from c in north.Customers select c.City)
.Intersect(from s in north.Suppliers select s.City);
foreach (var r in result)
{
Console.WriteLine(r);
}</pre>
<p>Öncelikli olarak ilk parantezler arasında <strong>Customers</strong> koleksiyonundaki her bir <strong>Customer</strong> nesnesinin <strong>City</strong> değerleri çekilmektedir. İkinci parantez içerisinde yapılanda benzerdir. Tek farkı <strong>Suppliers</strong> koleksiyonu için çalışmakta olmasıdır. <strong>Intersect</strong> metodunun burada getridiği kolaylık ise şudur. <strong>Customers</strong> ve <strong>Suppliers</strong> koleksiyonlarında bulundan ortak <strong>City</strong> özelliklerinin elde edilmesini sağlamaktadır. Bir başka deyişle yine SQL' ciler gibi konuşacak olursak, müşterilerin ve tedarikçilerin bir arada bulunduğu şehirlerin elde edilmesi sağlanmaktadır . Buna göre örneğin çalışma zamanındaki ekran çıktısı aşağıdaki gibi olacaktır.</p>
<p><img src="/makale/images/mk236_22.gif" alt="" width="277" height="112" border="0" /></p>
<p>Bu sonucun elde edilmesi için arka planda çalıştırılan SQL cümlesi ise aşağıdaki gibidir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">SELECT DISTINCT [t0].[City]
FROM [dbo].[Customers] AS [t0]
WHERE EXISTS(
SELECT NULL AS [EMPTY]
FROM [dbo].[Suppliers] AS [t1]
WHERE
(([t0].[City] IS NULL)
AND ([t1].[City] IS NULL))
OR (([t0].[City] IS NOT NULL)
AND ([t1].[City] IS NOT NULL)
AND ([t0].[City] = [t1].[City]))
)</pre>
<p>Çalıştırılan SQL sorgusu, LINQ tarafında <strong>Except</strong> metodu kullanıldığı zamankine benzerdir. Tek fark burada kesişim kümesinin bulunması gerektiğinden <strong>Not Exists</strong> kullanılmamış olmasıdır.</p>
<p>Makalemizde son olarak <strong>?:</strong> operatörünün kullanıldığı bir durumu ele almaya çalışıyor olacağız. Bu operatör şu aşamada LINQ' e bağımlı olmayan <strong>C#</strong> programlama dilinin ilk versiyonundan beri var olan bir araçtır. Bu tip bir operatörün <strong>LINQ</strong> ifadesi içerisinde kullanılması haline <strong>SQL</strong> tarafında oluşacak olan cümlelere bakmaya çalışıyor olacağız. Bu amaçla aşağıdaki kod parçasını geliştirdiğimizi düşünelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var result = from prd in adw.Products
select new
{
prd.Name
,prd.SafetyStockLevel
,LevelOk = prd.SafetyStockLevel >= 50 ? "Seviye İyi" : "Seviye Düşük"
};
foreach (var p in result)
{
Console.WriteLine(p.Name + " | " + p.SafetyStockLevel + " | " + p.LevelOk);
}</pre>
<p>Bu kod parçasında kullanılan LINQ ifadesine bakıldığında, <strong>LevelOk</strong> isimli isimsiz tip özelliğinin değerinin <strong>SafetyStockLevel</strong> özelliğinin değerine göre belirlendiği görülmektedir. <strong>SafetyStockLevel</strong> özelliğinin değerinin 50 ve üzerinde olması halinde LevelOk özelliğine Seviye İyi değeri atanmaktadır. Aksi durumda ise Seviye Düşük değeri atanmaktadır. Kodun çalışma zamanındaki çıktısı aşağıdaki gibi olacaktır.</p>
<p><img src="/makale/images/mk236_23.gif" alt="" width="382" height="340" border="0" /></p>
<p>SQL tarafına baktığımızda ise aşağıdaki sorgu cümlesinin çalıştırıldığı görülmektedir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'SELECT [t0].[Name], [t0].[SafetyStockLevel],
(CASE
WHEN [t0].[SafetyStockLevel] >= @p0 THEN CONVERT(NVarChar(12),@p1) ELSE @p2 END
)
AS [LevelOk]
FROM [Production].[Product] AS [t0]'
,N'@p0 int,@p1 nvarchar(10),@p2 nvarchar(12)',@p0=50,@p1=N'Seviye İyi',@p2=N'Seviye Düşük'</pre>
<p>Dikkat edileceği üzere, <strong>LevelOk</strong> alanının elde edilmesi sırasında<strong> Case When SQL</strong> ifadesi kullanılmaktadır.</p>
<p>Buraya kadar anlatılan örneklerde LINQ operatörlerinden veya metodlarından bir kısmının SQL tarafına nasıl aktarıldıkları incelenmeye çalışılmıştır. Diğer taraftan makalemizin başındada belirtildiği üzere programatik tarafta kullanılan her tür LINQ operatörü veya fonksiyonunun SQL tarafına aktarılmasıda mümkün değildir. Söz gelimi aşağıdaki kod parçasını ele aldığımızı düşünelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var result = (from ctg in adw.ProductSubcategories select ctg)
.TakeWhile<ProductSubcategory>(sCtg => sCtg.Name[0] == 'A');
foreach (ProductSubcategory sc in result)
{
Console.WriteLine(sc.Name + " " + sc.ProductSubcategoryID.ToString());
}</pre>
<p>Bu kod parçası yürütülmek istendiğinde çalışma zamanında(run time), aşağıdaki ekran görüntüsündende izlenebileceği gibi <strong>NotSupportedException</strong> tipinden bir <strong>istisna(Exception) </strong> alınmaktadır.</p>
<p><img src="/makale/images/mk236_12.gif" alt="" width="482" height="155" border="0" /></p>
<p>Nitekim TakeWhile metodunun SQL tarafında bir karşılığı yoktur. <strong>TakeWhile</strong> gibi <strong>SkipWhile</strong>, <strong>Last</strong>, <strong>ElementAt</strong>, <strong>Reverse</strong> gibi pek çok metodunda SQL tarafında karşılığı bulunmadığından desteklenmemektedirler.</p>
<p>Sonuç olarak programatik tarafta <strong> varlık katmanı(Entity Layer)</strong> üzerinde işlemlerimizi oldukça kolaylaştıran ve nesneler üzerinde sorgular çalıştırabilmemizi sağlayan <strong>LINQ to SQL</strong>' in gücü ortadadır. Ne varki performansın öne geçmesi gereken durumlarda, yazılan LINQ ifadelerinin arka planda oluşturduğu SQL çıktıları değerlendirilmeli ve en doğru şekilde kullanılmalarına gayret edilmelidir. Zaten zaman içerisinde benzer vakalar için en uygun LINQ söz dizimlerinin ne olacağı daha net bir şekilde ortaya çıkacaktır. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://www.buraksenyurt.com/makale/images/DahaFazlaLINQSorgusu2.rar">Örnek Uygulama için Tıklayın</a></p>2007-12-19T10:00:00+00:00linq to sqllinqbsenyurtVeritabanı(Database) nesnelerinin programatik ortamda sınıf gibi tipler(Type) ve metod benzeri üyeler(Members) ile ifade ediliyor olması, bu tiplere ait nesne örnekleri üzerinden sorgulalamalar yapılabilmesi ihtiyacınıda ortaya çıkartmıştır. Bir veritabanı nesnesinin programatik taraftaki karşılığının nesne yönelimli(Object Oriented) bir dilde geliştirilmesi son derece kolaydır. Örneğin bir tablo(Table) göz önüne alındığında, bu tablonun kendisi bir sınıf(Class) olarak tasarlanabilir. Benzer şekilde, tablo içerisindeki alanlar(Fields) sınıf içinde yer alan birer özellik(Property) olarak düşünülebilir.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=63c7c020-bb9b-4ebc-bc9c-dd7dfdde55d61https://www.buraksenyurt.com/trackback.axd?id=63c7c020-bb9b-4ebc-bc9c-dd7dfdde55d6https://www.buraksenyurt.com/post/Linq-To-Sql-Arka-Planda-Neler-Oluyor-bsenyurt-com-dan#commenthttps://www.buraksenyurt.com/syndication.axd?post=63c7c020-bb9b-4ebc-bc9c-dd7dfdde55d6https://www.buraksenyurt.com/post/LINQ-to-SQL-ile-CRUD-Islemleri-bsenyurt-com-danLINQ to SQL ile CRUD İşlemleri2007-12-15T08:00:00+00:00bsenyurt<p>Değerli Okurlarım Merhabalar,</p>
<p><strong>Language Integrated Query(LINQ) </strong>mimarisi özellikle programatik ortamlarda tasarlanan nesneler üzerinde, <strong>SQL</strong> cümlelerine benzer ifadeler ile sorgulamalar yapılmasına izin vermektedir. Çok doğal olarak <strong>veritabanı(database)</strong> tarafında yer alan <strong>tablo(Table),</strong> <strong>saklı yordam(Stored Procedure), görünüm(View), fonksiyon(Function)</strong> gibi unsurlarında programatik tarafta birer <strong>varlık(Entity)</strong> olarak ifade edilebilmesi, LINQ kurallarının SQL üzerindede gerçekleştirilebilmesini sağlamaktadır. Burada <strong>varlık katmanı(Entity Layer)</strong> olarakda düşünebileceğimiz yapı üzerinde yer alan nesneler, veritabanından çekilen sonuçları saklayabilmektedir. Bunun yanında programatik ortamdaki varlıklar üzerinde yeni varlık oluşturma, güncelleme, silme gibi operasyonlarda yapılabilmektedir. İşte bu makalemizde çoğunlukla <strong>CreateRetrieveUpdateDelete (CRUD)</strong> işlemleri olarak belirtilen bu operasyonları nasıl yapabileceğimizi, adım adım basit örnekler üzerinden incelemeye çalışıyor olacağız. <em>(Bu makalede geliştirilmekte olan örnek kod parçaları <strong>Visual Studio 2008 RTM</strong> ortamında yazılmıştır.)</em></p>
<p>İlk olarak örnek bir <strong>SQL</strong> veritabanındaki <strong>tablo(table)</strong> yapılarını programatik ortamda taşıyacak olan sınıfların üretilmesi gerekmektedir. Bu amaçla <strong>Visual Studio 2008</strong> üzerinde basit bir <strong>Console</strong> uygulaması açarak ilerleyebiliriz. Burada dikkat edilmesi gereken önemli noktalardan birisi <strong>New Project</strong> seçimi sonrası karşımıza çıkacak olan iletişim penceresinden <strong>.Net Framework 3.5</strong> versiyonunun işaretlenmiş olmasıdır.</p>
<p><img src="/makale/images/mk235_1.gif" alt="" width="553" height="157" border="0" /></p>
<p>Bu şart olmamakla birlikte, <strong>LINQ</strong> için gerekli olan <strong>assembly</strong>' ların(örneğin <strong>System.Data.DataSetExtensions</strong> gibi) otomatik olarak referans edilmesini sağlamaktadır. Bu adımdan sonra <strong>entity</strong> sınıflarının kolay bir şekilde oluşturulmasını sağlayan <strong>LINQ To SQL Class</strong> öğesini projemize eklememiz gerekmektedir.<em>(LINQ to SQL sınıflarının oluşturulması ile ilişkili detaylı bilgiyi C#Nedir? yer alan <a href="http://www.csharpnedir.com/videoindir.asp?id=71">görsel</a> dersten öğrenebilirsiniz.)</em></p>
<p><img src="/makale/images/mk235_2.gif" alt="" width="630" height="345" border="0" /></p>
<p>Böylece veritabanı üzerindeki nesnel yapıları programatik ortamda ifade edebileceğimiz <strong>Database Markup Language(dbml)</strong> dosyası otomatik olarak oluşturulmaktadır. <strong>Adventure.dbml </strong>dosyasnın kod tarafına bakıldığında <strong>DataContext</strong> tipinden türetilmiş olan bir sınıfın yazıldığı görülmektedir. Şimdi yapmamız gereken, üzerinde işlemler gerçekleştirilecek olan veritabanı nesnelerini tasarım ortamına sürükleyip bırakmaktır. İlk etapta örneğin basit olması açısından sadece <strong>Production</strong> şemasında(schema) yer alan <strong>ProductCategory</strong> isimli tablo tasarım ortamına, <strong>Server Explorer</strong> pencersinden sürüklenmiştir.</p>
<p><img src="/makale/images/mk235_3.gif" alt="" width="531" height="299" border="0" /></p>
<p>Bu basit operasyonun ardından aşağıdaki <strong>sınıf diagramında(Class Diagram)</strong> olduğu gibi <strong>ProductCategory</strong> için bir tipin yazıldığı ve bununla ilişkili olaraktanda, <strong>DataContext</strong> tipinden türeyen <strong>AdventureDataContext</strong> sınıfı içerisine <strong>ProductCategories</strong> isimli bir özelliğin(Properties) atıldığı görülmektedir. ProductCategories özelliği generic Table<ProductCategory> tipinden bir nesne referansını işaret etmektedir. Bu generic tip tahmin edileceği üzere ProductCategory tabosundaki tüm içeriği taşıyan sınıftır.</p>
<p><img src="/makale/images/mk235_4.gif" alt="" width="565" height="415" border="0" /></p>
<p>Artık bu noktadan sonra <strong>AdventureDataContext</strong> sınıfına ait nesne örneklerini kullanarak kategorilerin elde edilmesi, sorgulanması, yeni kategorilerin oluşturulması(Insert), var olanlardan bir veya bir kaçının silinmesi(Delete) yada güncellenmesi(Update) gibi işlemler kolaylıkla yapılabilir. İlk olarak yeni bir kategoriyi nasıl ekleyebileceğimizi örnek bir kod parçası üzerinden incelemeye çalışalım. Bu amaçla <strong>Main</strong> metodu içerisine aşağıdaki kod satırlarını eklediğimizi düşünelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">AdventureDataContext adwContext = new AdventureDataContext();
ProductCategory tools = new ProductCategory()
{
Name = "Tools"
, ModifiedDate=new DateTime(2001,1,1)
};
adwContext.ProductCategories.InsertOnSubmit(tools);</pre>
<p>İlk olarak <strong>AdventureDataContext</strong> tipine ait bir nesne örneği oluşturulmaktadır. Bu işlemin arkadasındanda eklenmek istenen <strong>ProductCategory</strong> nesne örneği <strong>C# 3.0</strong> ile birlikte gelen <strong>nesne başlatıcılarından(Object Initializers)</strong> yararlanılarak örneklenmektedir. Bu noktada <strong>Table<T> </strong>generic tipinden olan <strong>ProductCategories</strong> sınıfının <strong>InsertOnSubmit</strong> metodu, koleksiyonuna ilave edilmek üzere yeni bir ProductCategory nesne örneğinin eklenmesi için kullanılmaktadır. Burada üzerinde durulması gereken önemli bir nokta vardır. <strong>InsertOnSubmit</strong> metodu sadece Table<T> koleksiyonuna ilave edilmek üzere bir nesne eklemektedir. Bir başka deyişle ProductCategories koleksiyonu üzerinde yapılacak bir for döngüsünde eklenen nesne(ler) görülmeyecektir. Bunu test etmek için aşağıdaki gibi bir kod parçasını ele alabiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var categories = from category in adwContext.ProductCategories
select category.Name;
foreach(string ctgr in categories)
Console.WriteLine(ctgr);</pre>
<p>Bunun sonucu olarak çalışma zamanında(run time) aşağıdaki ekran görüntüsünü elde ederiz.</p>
<p><img src="/makale/images/mk235_5.gif" alt="" width="302" height="136" border="0" /></p>
<p>Burada sebep son derece açıktır. <strong>var</strong> anahtar kelimesinden(keyword) sonra <strong>foreach</strong> döngüsünün iterasyona başlamasıyla birlikte <strong>SQL</strong> sunucusu üzerinde bir <strong>select</strong> sorgusu çalışamaktadır. Bu sorgu <strong>SQL Profiler</strong> yardımıyla kolay bir şekilde yakalanabilir. Aşağıdaki ekran görüntüsünde bu durum görülmektedir.</p>
<p><img src="/makale/images/mk235_6.gif" alt="" width="515" height="302" border="0" /></p>
<p>Dolayısıyla <strong>eklenmek(Insert)</strong> üzere ilave edilen ProductCategory nesne örneği henüz veritabanına doğru gönderilmemiştir. Bu işlemin nasıl yapıldığını görmeden önce koleksiyona eklenen ve insert işlemi için sırada bekleyen nesne örneklerini nasıl elde edebileceğimize bakalım. Burada <strong>DataContext</strong> sınıfına ait <strong>GetChangeSet</strong> metodundan yararlanılmaktadır. Bu metod ile elde edilen <strong>ChangedSet</strong> nesne örneği üzerinden <strong>Inserts,Deletes</strong> veya <strong>Updates</strong> <strong> özellikleri(Properties)</strong> kullanılarak eklenen, silinen veya güncellenen örnekler yakalanabilir.</p>
<table id="table181" style="width: 100%;" border="1" cellspacing="0" cellpadding="5" bgcolor="#ffffff">
<tbody>
<tr>
<td valign="top" width="53"><img src="/makale/images/dikkat.gif" alt="" width="53" height="53" border="0" /></td>
<td><em><strong>Inserts</strong>, <strong>Deletes</strong> ve <strong>Updates</strong> özellikleri geriye <strong>IList<T></strong> tipinden bir referans döndürmektedir. Bu referans bilgisinden yararlanılarak eklenen, silinen veya güncellenen tüm nesnelerin yakalanması mümkündür. <strong>IList<T></strong> <strong>arayüzü(Interface) .Net 2.0</strong> versiyonundan beri mevcuttur. Ayrıca <strong>IEnumerable<T></strong> arayüzünden türemiş olduğundan, elde edilen referans üzerinde <strong>LINQ</strong> sorgularıda yazılabilir. Bu sayede eklenen, silinen veya güncellenen nesnelerin veritabanına yazılmadan önce sorgulanmalarıda mümkün olmaktadır.</em></td>
</tr>
</tbody>
</table>
<p><br /><img src="/makale/images/mk235_7.gif" alt="" width="337" height="160" border="0" /></p>
<p>Örnek uygulamada bu kod parçası denenirse eklenen tool isimli yeni kategorininde elde edildiği görülecektir.</p>
<p><img src="/makale/images/mk235_8.gif" alt="" width="469" height="202" border="0" /></p>
<p>Çok doğal olarak <strong>ProductCategoryID</strong> gibi alanlar veritabanı üzerindeki tabloda otomatik olarak üretildiklerinden ve kod içerisinde herhangibir değer atanmadığından henüz oluşturulmamıştır. Yapılan bu ekleme(Insert) işleminin veritabanına gönderilmesi için tek yapılması gereken ise <strong>DataContext</strong> tipinin <strong>SubmitChanges</strong> metodunu çalıştırmaktır.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">adwContext.SubmitChanges();</pre>
<p><strong>SubmitChanges</strong> metodu çalıştırıldığında <strong>Insert</strong>, <strong>Update</strong> veya <strong>Delete</strong> kuyruğunda bekleyen tüm nesneler için gerekli <strong>SQL</strong> <strong>sorguları(Queries)</strong> yürütülmektedir. Söz gelimi yukarıdaki örneğe göre <strong>SQL Profiler</strong> ile arkada çalışan kodlar izlenirse aşağıdaki sonuçların elde edildiği görülecektir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'INSERT INTO [Production].[ProductCategory]([Name], [rowguid], [ModifiedDate])
VALUES (@p0, @p1, @p2)
SELECT CONVERT(Int,SCOPE_IDENTITY()) AS [value]',N'@p0 nvarchar(5),@p1 uniqueidentifier,@p2 datetime',@p0=N'Tools',@p1='00000000-0000-0000-0000-000000000000',@p2='2001-01-01 00:00:00:000'</pre>
<p>Bu çalışan sorgu(Query) basit olarak üretilen varlık nesnesinin(Entity Object) veritabanına doğru yazılmasını sağlamaktadır. İstenirse toplu olarak veri ekleme işlemleride gerçekleştirilebilir. Bunun için tek yapılması gereken <strong>InsertAllOnSubmit</strong> metodunu kullanmaktadır. Aşağıdaki örnek kod parçasında toplu bir ekleme işleminin nasıl yapılabileceği gösterilmektedir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">adwContext.ProductCategories.InsertAllOnSubmit(
new List<ProductCategory>()
{
new ProductCategory(){Name="Kategori X", ModifiedDate=new DateTime(2006,12,3),rowguid=Guid.NewGuid()}
,new ProductCategory(){Name="Kategori Y",ModifiedDate=new DateTime(2006,5,6),rowguid=Guid.NewGuid()}
,new ProductCategory(){Name="Kategori Z",ModifiedDate=new DateTime(2007,1,4),rowguid=Guid.NewGuid()}
}
);
var eklenenler = adwContext.GetChangeSet().Inserts;
foreach (ProductCategory eklenen in eklenenler)
Console.WriteLine(eklenen.Name);
adwContext.SubmitChanges();</pre>
<p>Yine nesne başlatıcılarından(Object Initializers) yararlanılarak, <strong>InsertAllOnSubmit</strong> metodu içerisinde <strong>ProductCategory</strong> tipinden örnekler alabilecek generic List koleksiyonu oluşturulmuş ve 3 örnek ProductCategory eklenmiştir. İlave edilen satırları elde edebilmek için yine <strong>GetChangeSet</strong> metodu üzerinden <strong>Inserts</strong> özelliği kullanılmaktadır. <strong>DataContext</strong> tipi üzerinden <strong>SubmitChanges</strong> metodunun çalıştırılması ile birlikte, <strong>varlık(Entity) </strong>tiplerine eklenen 3 ProductCategory nesnesi içinde birer <strong>Insert</strong> sorgusu <strong>SQL</strong> sunucusu üzerinde yürütülmektedir. Bu durumu daha iyi analiz etmek için <strong>SQL Profiler </strong>aracı kullanıldığında aşağıdakine benzer sonuçlar alındığı görülür.</p>
<p><img src="/makale/images/mk235_9.gif" alt="" width="546" height="357" border="0" /></p>
<p>Dikkat edileceği üzere her bir varlık nesnesi(Entity Object) için birer <strong>Insert</strong> sorgusu yürütülmektedir.</p>
<p>Gelelim silme işlemlerine. Silme(Delete) operasyonlarındada ekleme işlemlerine benzer şekilde <strong>DeleteOnSubmit</strong> ve <strong>DeleteAllOnSubmit</strong> gibi metodlar yer almaktadır. Herzamanki gibi varlık nesneleri üzerinden yapılan silme işlemleri <strong>GetChangeSet</strong> metodu üzerinden ulaşılan <strong>Deletes</strong> özelliği yardımıyla elde edilebilir. Aşağıdaki örnek kod parçasında tek bir satırın silme işleminin nasıl yapılabileceği gösterilmektedir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">ProductCategory silinecekVeri = (from cat in adwContext.ProductCategories
where cat.ProductCategoryID == 25
select cat).Single<ProductCategory>();
adwContext.ProductCategories.DeleteOnSubmit(silinecekVeri);
var silinenler = adwContext.GetChangeSet().Deletes;
foreach(ProductCategory silinen in silinenler)
Console.WriteLine(silinen.Name);
adwContext.SubmitChanges();</pre>
<p>Burada dikkat edilmesi gerken bazı noktalar vardır. Silinmek istenilen satır veya satırların öncelikli olarak bulunması gerekir. Bu çok doğal olarak <strong>Table<T> </strong>tipi üzerinden bir <strong>LINQ</strong> sorgusu ile mümkün olabilir. Dolayısıyla yukarıdaki kod parçasına göre, silinecekVeri isimli koleksiyon elde edilirken arka planda aşağıdaki sorgu cümlesi çalışacaktır.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'SELECT TOP (1) [t0].[ProductCategoryID], [t0].[Name], [t0].[rowguid], [t0].[ModifiedDate]
FROM [Production].[ProductCategory] AS [t0]
WHERE [t0].[ProductCategoryID] = @p0',N'@p0 int',@p0=25</pre>
<p>Bu adımdam sonra <strong>DeleteOnSubmit</strong> metoduna, silinmek istenen <strong>varlık(Entity)</strong> örneği parametre olarak verilmektedir. Bu işlem sadece silinecek olan veriler için kuyruğa bir ekleme yapmaktadır. Öyleki bu metod çağırıldıktan sonra <strong>ProductCategories</strong> koleksiyonuna bakılırsa, 25 numaralı <strong>ProductCategory</strong> tipinin halen daha mevcut olduğu görülebilir.</p>
<p><img src="/makale/images/mk235_10.gif" alt="" width="478" height="339" border="0" /></p>
<p>Silinmek istenen verinin programatik ortamda elde edilmesi için tek yapılması gereken <strong>GetChangeSet</strong> metodu üzerinden <strong>Deletes</strong> özelliğine ulaşmaktır. Çalışma zamanı <strong>SubmitChanges</strong> metodunu yürüttüğünde ise silinmek istenen <strong>entity</strong> nesnesi ile ilgili olaraktan aşağıdaki sorgu cümlesi <strong>SQL</strong> tarafında işletilecektir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'DELETE FROM [Production].[ProductCategory]
WHERE
([ProductCategoryID] = @p0) AND ([Name] = @p1) AND ([rowguid] = @p2) AND ([ModifiedDate] = @p3)',N'@p0 int,@p1 nvarchar(10),@p2 uniqueidentifier,@p3 datetime',@p0=25,@p1=N'Kategori Y',@p2='B68CEBC9-CFFF-4AD5-9F5A-ADEC8823E88A',@p3='2006-06-05 00:00:00:000'</pre>
<p>Sorgu cümlesindende dikkat edileceği üzere <strong>Where</strong> ifadesinden sonra tüm alanlar hesaba katılmaktadır. Bunun sebebi <strong>LINQ to SQL </strong>mimarisinin <strong>Optimistic(İyimser) Concurrency</strong> modelini kullanmasıdır. Bu modellde bilindiği üzere kontrol edilebilir tüm alanlar <strong>Where</strong> ifadesinden sonra hesaba katılmaktadır. Bir başka deyişle model, veriyi başkasının silip silmediğini, güncelleştirip güncelleştirmediğini araştırmaktadır.</p>
<p>Örnekte 25 numaralı kategoriye ait satırı silmeden önce başka birisi değiştirmişse eğer, çalışma zamanında <strong>ChangeConflictException</strong> istisnası alınır. Bu durumu daha iyi analiz etmek için 25 numaralı satırı silmek istediğimizi göz önüne alalım. <strong>SubmitChanges</strong> metodu çağırılmadan öncede veritabanından manuel olarak yada başka bir program üzerinden 25 numaralı satırın <strong>Name</strong> alanının değerini Kategori X' den Kategori XL' ye değiştirelim. Bunu kolay bir şekilde gerçekleştirmek için <strong>SubmitChanges</strong> satırına <strong>breakpoint</strong> koyup ilerlemeden önce tabloda değişiklik yapmak yeterli olacaktır. Böyle bir durumda çalışma zamanında(run time) aşağıdaki gibi bir durum olaşacaktır.</p>
<p><img src="/makale/images/mk235_11.gif" alt="" width="481" height="158" border="0" /></p>
<p>Nitekim biz silmek istediğimiz veriyi çektikten sonra <strong>Name</strong> alanının değeri Kategori X iken, <strong>SubmitChanges</strong>' den önce Kategori XL' ye değiştirilmiştir. Buda doğal olarak <strong>Where</strong> ifadesinin geçersiz olması anlamına gelmektedir. Ancak bu durum istenirse değiştirilebilir. Öyleki, <strong>varlık(Entity) </strong>sınıflarında yer alan <strong>özelliklerin(Properties) Column</strong> isimli <strong>niteliklerine(Attribute) </strong>ait <strong>ColumnChange</strong> özelliğinin değeri <strong>Never</strong> yapıldığında söz konusu özelliklerin Where ifadelerinden sonrasına katılmadığı görülmektedir. Söz gelimi örnekte yer alan ProductCategory sınıfının <strong>Name</strong>, <strong>rowguid</strong> ve <strong>ModifiedDate</strong> özelliklerinde yer alan <strong>Column</strong> niteliğinde aşağıdaki gibi bir değişiklik yaptığımızı düşünelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">[Column(Storage="_Name", DbType="NVarChar(50) NOT NULL", CanBeNull=false,UpdateCheck=UpdateCheck.Never)]
public string Name</pre>
<p><strong>UpdateCheck</strong> değerine <strong>Never</strong> atanması sonucu söz konusu özelliğin değeri <strong>WHERE</strong> ifadesine parametre olarak dahil edilmeyecektir. Bu işlemin ardından örnek olarak başka bir satırı daha silmek istersek arka tarafta çalışan <strong>Delete</strong> sorgusunun aşağıdaki gibi oluşturulduğunu görebiliriz.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'DELETE FROM [Production].[ProductCategory]
WHERE [ProductCategoryID] = @p0',N'@p0 int',@p0=29</pre>
<p>Görüldüğü gibi sadece <strong>ProductCategoryID</strong> alanı <strong>WHERE</strong> ifadesinden sonrasına katılmıştır. <em>(Kodun bundan sonraki kısımlarında Optimistic Concurrency modeline göre ilerleneceğinden UpdateCheck değişiklikleri geri alınmıştır.)</em></p>
<p>İstenirse toplu olarak silme işlemleride gerçekleştirilebilir. Örneğin Name özelliğinin içerisinde Kategori kelimesi geçen satırları silmek istediğimizi düşünelim. Bu amaçla aşağıdaki gibi bir kod parçası göz önüne alınabilir. Öncelikli olarak Contains metodu ile Kategori kelimesi geçen ProductCategory nesnelerinin bir listesinin elde edilemsi gerekmektedir. LINQ sorgusu buna göre düzenlenmiştir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var kategoriGecenler = from k in adwContext.ProductCategories
where k.Name.Contains("Kategori")
select k;
adwContext.ProductCategories.DeleteAllOnSubmit<ProductCategory>(kategoriGecenler);
adwContext.SubmitChanges();</pre>
<p>Söz konusu kod içerisinde <strong>SubmitChanges</strong> metodu çalıştırıldığında <strong>SQL</strong> tarafında, silinmek istenen her satır için bir <strong>Delete</strong> sorgu ifadesinin yürütüldüğü görülecektir. Örnekte bu kategoriye uyan 5 satır bulunmaktadır.</p>
<p><img src="/makale/images/mk235_12.gif" alt="" width="608" height="309" border="0" /></p>
<p>Gelelim <strong>güncelleme(Update) </strong>işlemlerine. Güncelleme süreçlerinde, <strong>Insert</strong> ve <strong>Delete</strong> işlemlerindeki gibi metodlar söz konusu değildir. Nitekim güncelleme işlemi aslında varlık nesnesinin herhangibir özelliğinin(özelliklerinin) değerinin değiştirilmesinden başka bir şey değildir. Dolayısıyla tek yapılması gereken değişiklikler tamamlandıktan sonra <strong>SubmitChanges</strong> metodunu çağırmaktır. Aşağıdaki örnek kod parçasında örnek olarak <strong>Product</strong> tablosuna ait bir <strong>varlık sınıfı(Entity Class)</strong> kullanılmaktadır. Bu sınıfı oluşturmak için tek yapılması gereken tahmin edileceği üzere <strong>Server</strong> <strong>Explorer</strong>' dan <strong>Product</strong> tablosunu <strong>Adventure.dbml </strong>üzerine tasarım zamanında sürükleyip bırakmaktır.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var guncellenecekler = from p in adwContext.Products
where p.ProductSubcategoryID == 1
select p;
foreach (Product prd in guncellenecekler)
prd.ListPrice += 10;
var bekleyenGuncellemeler = adwContext.GetChangeSet().Updates;
adwContext.SubmitChanges();</pre>
<p>Bu kod parçasında <strong>ProductSubCategoryId</strong> değeri 1 olan nesneler elde edilmekte ve herbirinin <strong>ListPrice</strong> değeri 10 birim arttırılmaktadır. Çok doğal olarak yapılan bu güncellemelerde <strong>Updates</strong> kuyruğuna atılacaktır. Söz gelimi örneğe göre varsayılan olarak kuyruğa 32 Product nesnesi atılmaktadır. Elbette koşula uyan kategorilerin her birinin ListPrice özelliklerinin değerleri değiştirildiğinde bu güncellemeler <strong>Products</strong> koleksiyonunada yansıyacaktır.</p>
<p><img src="/makale/images/mk235_13.gif" alt="" width="471" height="273" border="0" /></p>
<p><strong>SubmitChanges</strong> metodunun işletilmesi ile birlikte güncelleme kuyruğunda bekleyen 32 adet <strong>Product</strong> nesne örneği için veritabanında <strong>Update</strong> sorguları çalıştırılacaktır. Aşağıdaki<strong> SQL Profiler</strong> ekran görüntüsünde bu sorguların bir kısmı yer almaktadır.</p>
<p><img src="/makale/images/mk235_14.gif" alt="" width="519" height="433" border="0" /></p>
<p>Buraya kadarki kısımda basit olarak<strong> ekleme(Insert), silme(Delete) ve güncelleme(Update) </strong>işlemlerini nasıl yapabileceğimizi görmeye çalıştık. Önemli olan noktalardan biriside değişikliklerden vazgeçersek ne olacağıdır. Bu önemli bir sıkıntıdır. Nitekim <strong>GetChangeSet </strong>metodu üzerinden elde edilen <strong>ChangeSet</strong> tipinin sunduğu <strong>Deletes</strong>, <strong>Inserts</strong>, <strong>Updates</strong> özelliklerinin döndürdğü <strong>IList<T></strong> koleksiyonları çalışma zamanında <strong>yanlız okunabilir(read-only) </strong> şekilde ele alınabilmektedir. Bu nedenle <strong>Clear</strong>, <strong>Remove</strong>, <strong>RemoveAt</strong> gibi metod çağrıları hatta <strong>null</strong> değer atanması sonrası çalışma zamanı istisnaları alınmaktadır. Söz gelimi son örnek koddaki güncelleştirmeleri onaylamak istemediğimizi düşünelim. Bu amaçla <strong>Clear</strong> metoduna başvurulması düşünülebilir. Ancak bu durumda çalışma zamanında aşağıdaki ekran görüntüsünde yer alan <strong>NotSupportedException</strong> istisnası alınacaktır.</p>
<p><img src="/makale/images/mk235_15.gif" alt="" width="423" height="96" border="0" /></p>
<p>Bu noktada <strong>Table<T></strong> tipi üzerinden ulaşılabilen <strong>GetOriginalEntityState</strong> metodu alternatif bir yol olarak ele alınabilir. Nitekim bu metod, parametre olarak verilen <strong>varlık nesnesinin(Entity Object) </strong>değiştirilmeden önceki halini elde etmemizi sağlamaktadır. Örneğin aşağıdaki kod parçasını ele alalım.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var bekleyenGuncellemeler = adwContext.GetChangeSet().Updates;
Product prdNew = (Product)bekleyenGuncellemeler[0];
Console.WriteLine(prdNew.ListPrice.ToString());
Product prdOriginal = adwContext.Products
.GetOriginalEntityState((Product)bekleyenGuncellemeler[0]);
Console.WriteLine(prdOriginal.ListPrice.ToString());</pre>
<p>Burada görüldüğü gibi prdNew nesne örneğinin ListPrice değeri 3419.99 iken prdOriginal' in değeri 3409.00 dur. Aşağıdaki çalışma zamanı görüntüsünde bu durum net bir şekilde görülebilmektedir.</p>
<p><img src="/makale/images/mk235_16.gif" alt="" width="462" height="298" border="0" /></p>
<p>Ancak bu teknik yardımıyla güncellenmiş olan tüm satırların geri alınması oldukça zordur. Nitekim<strong> Table<T> </strong>tipinin <strong>yapıcı metodunun(Constructor)</strong> kullanılamadığı, bu yüzden <strong>new</strong> ile üretilemediği ortadadır. Ayrıca var olan <strong>DataContext</strong> nesnesinin Table<T> tipinden özellikleri <strong>ReadOnly</strong>' dir. Bir başka deyişle bu özelliklere doğrudan atama da yapılamamaktadır. Sonuç olarak <strong>DataContext</strong> tipinin yeniden örneklenmesi sorunu çözmek için yeterli olacaktır.</p>
<table id="table182" style="width: 100%;" border="1" cellspacing="0" cellpadding="5" bgcolor="#ffffff">
<tbody>
<tr>
<td valign="top" width="53"><img src="/makale/images/dikkat.gif" alt="" width="53" height="53" border="0" /></td>
<td><em><strong>Table<T></strong> generic tipi üzerinden kullanılan <strong>GetModifiedMembers</strong> metodu ile, parametre olarak verilen <strong>varlık(entity)</strong> nesne örneğinin değişikliğe uğrayan değerlerinin <strong>orjinal(OriginalValue)</strong> ve <strong> anlık(CurrentValue)</strong> hallerinin elde edilmesi sağlanabilmektedir. GetModifiedMembers metodu geriye <strong>ModifiedMemberInfo</strong> tipinden bir dizi döndürmektedir. Aşağıdaki şekilde örnek olarak güncellenen satırlardan ilki için orjinal ve güncel <strong>ListPrice</strong> değerlerinin elde edilişi gösterilmektedir.</em>
<p><img src="/makale/images/mk235_17.gif" alt="" width="459" height="149" border="0" /></p>
</td>
</tr>
</tbody>
</table>
<p><br />Normal şartlarda <strong>SubmitChanges</strong> metodunun çağırılmasından sonra güncelleme, ekleme ve silme işlemleri otomatik olarak transaction içerisinde çalıştırılırlar. Bir başka deyişle SubmitChanges metodu, veritabanı üzerinde yapılacak işlemlerin, biz söylemeden otomatik olarak bir <strong>transaction</strong> içerisinde olmasını sağlamaktadır. Söz gelimi aşağıdaki kod parçasını ele alalım. Bu kod parçasında güncelleme(Update) ve yeni ürün ekleme(Insert) işlemleri söz konusudur.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var guncellenecekler = from p in adwContext.Products
where p.Class == "M" && p.ProductSubcategoryID==1
select p;
foreach (Product prd in guncellenecekler)
prd.ListPrice += 10;
Product newProduct = new Product()
{
Name = "Yeni Urun"
, ProductSubcategoryID = 1, Color = "Red"
, Class = "M", ListPrice = 100
, ProductNumber = "PRD-1204", ReorderPoint = 10
, StandardCost = 90, ProductModelID = 123
, SafetyStockLevel = 45,SellStartDate=new DateTime(2007,1,1)
, SellEndDate=new DateTime(2008,1,1), DiscontinuedDate=new DateTime(2006,6,6)
, ModifiedDate=DateTime.Now
};
adwContext.Products.InsertOnSubmit(newProduct);
adwContext.SubmitChanges();</pre>
<p>İlk olarak Class değeri M ve ProductSubCategoryID değeri 1 olan Product tiplerine ait bir koleksiyon çekilmektedir. Daha sonra bu koleksiyon üzerinde dönülerek ListPrice değerleri sembolik olarak 10' ar birim arttırılmaktadır. Hemen arkasıdan örnek bir Product nesnesi oluşturulmakta ve InsertOnSubmit metodu ile eklenecekler listesine aktarılmaktadır. Uygulama çalıştırıldığında<strong> SQL Profiler</strong> aracılığıyla arkadaki işlemler incelenecek olursa aşağıdaki ekran görüntüsünde yer alan sonuçların elde edildiği görülür.</p>
<p><img src="/makale/images/mk235_18.gif" alt="" width="561" height="398" border="0" /></p>
<p>Dikkat edilecek olursa <strong>Insert</strong> ve <strong>Update </strong>ifadelerinin tamamı aynı <strong>Transaction</strong> içerisinde yürütülmektedir. Diğer taraftan aynı kodun aşağıdaki gibi değiştirildiğini düşünelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">var guncellenecekler = from p in adwContext.Products
where p.Class == "M" && p.ProductSubcategoryID==1
select p;
foreach (Product prd in guncellenecekler)
prd.ListPrice += 10;
adwContext.SubmitChanges();
Product newProduct = new Product()
{
Name = "Yeni Urun"
, ProductSubcategoryID = 1, Color = "Red"
, Class = "M", ListPrice = 100
, ProductNumber = "PRD-1204", ReorderPoint = 10
, StandardCost = 90, ProductModelID = 123
, SafetyStockLevel = 45,SellStartDate=new DateTime(2007,1,1)
, SellEndDate=new DateTime(2008,1,1), DiscontinuedDate=new DateTime(2006,6,6)
, ModifiedDate=DateTime.Now
};
adwContext.Products.InsertOnSubmit(newProduct);
adwContext.SubmitChanges();</pre>
<p>Burada SubmitChanges metodu güncelleme ve ekleme işlemlerinden sonra birer kez ayrı ayrı çağırılmaktadır. Bu durumda <strong>SQL Profiler</strong> aşağıdaki sonuçları üretecektir.</p>
<p><img src="/makale/images/mk235_19.gif" alt="" width="502" height="522" border="0" /></p>
<p>Görüldüğü gibi <strong>SubmitChanges</strong> her çağırıldığında o ana kadar gerçekleştirilen ne kadar ekleme, güncelleme veya silme işlemi varsa ayrı bir <strong>Transaction</strong> kapsamı içerisinde çalışmaktadır. Elbetteki istenirse son kod parçasındaki tüm sorgu işlemlerin aynı <strong>transaction kapsamı(Scope) </strong>içerisine dahil edilmeside sağlanabilir. Bunun için <strong>TransactionScope</strong> nesnesinden yararlanılabilir. Aşağıdaki örnek bu durum basit olarak ele alınmaktadır.<em>(<strong>TransactionScope</strong> sınıfının kullanılabilmesi için <strong>.Net Framework 2.0</strong> ile birlikte gelen <strong>System.Transactions.dll assembly'ının</strong> projeye referans edilmesi gerekmektedir.)</em></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using (TransactionScope tScope = new TransactionScope())
{
var guncellenecekler = from p in adwContext.Products
where p.Class == "M" && p.ProductSubcategoryID==1
select p;
foreach (Product prd in guncellenecekler)
prd.ListPrice += 10;
adwContext.SubmitChanges();
Product newProduct = new Product()
{
Name = "Yeni Urun"
, ProductSubcategoryID = 1, Color = "Red"
, Class = "M", ListPrice = 100
, ProductNumber = "PRD-1204", ReorderPoint = 10
, StandardCost = 90, ProductModelID = 123
, SafetyStockLevel = 45, SellStartDate=new DateTime(2007,1,1)
, SellEndDate=new DateTime(2008,1,1), DiscontinuedDate=new DateTime(2006,6,6)
, ModifiedDate=DateTime.Now
};
adwContext.Products.InsertOnSubmit(newProduct);
adwContext.SubmitChanges();
tScope.Complete();
}</pre>
<p>Dikkat edileceği üzere tüm kod parçası TransactionScope nesne örneğine ait bir <strong>using</strong> bloğu içerisine alınmış ve en sonra Complete metodu çağırılmıştır. Bu durumda <strong> SubmitChanges</strong> çağrıları ayrı ayrı yapılmış olsada tüm işlemler aynı <strong>transaction kapsamına(Transaction Scope</strong>) dahil edilecektir. <strong> TransactionScope</strong> kullanılması çok doğal olarak programatik taraftan <strong>Transaction</strong> ile ilgili daha fazla yönetsel işlemin yapılabilmesi anlamınada gelmektedir. Söz gelimi izolasyon seviyeleri(Isolation Level) daha kontrollü bir şekilde ele alınabilir. Hatta dağıtık transaction(Distributed Transaction) geçişleri daha kolay programlanabilir.</p>
<p><strong>TransactionScope</strong> kullanımı dışında yerel transaction kullanılarakta ilgili işlemlerin aynı <strong> Transaction</strong> içerisinde gerçekeştirilmesi sağlanabilir. Aşağıdaki örnek kod parçasında bu durum ele alınmaya çalışılmaktadır.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">try
{
var guncellenecekler = from p in adwContext.Products
where p.Class == "M" && p.ProductSubcategoryID == 1
select p;
foreach (Product prd in guncellenecekler)
prd.ListPrice += 10;
// Transaction başlatılması için bağlantının açık olması gerekir.
adwContext.Connection.Open();
// Transaction başlatılır ve AdventureContext tipine bildirilir.
adwContext.Transaction = adwContext.Connection.BeginTransaction();
adwContext.SubmitChanges();
Product newProduct = new Product()
{
Name = "Yeni Urun"
, ProductSubcategoryID = 1, Color = "Red"
, Class = "M", ListPrice = 100
, ProductNumber = "PRD-1204", ReorderPoint = 10
, StandardCost = 90, ProductModelID = 123
, SafetyStockLevel = 45, SellStartDate=new DateTime(2007,1,1)
, SellEndDate=new DateTime(2008,1,1), DiscontinuedDate=new DateTime(2006,6,6)
, ModifiedDate=DateTime.Now
};
adwContext.Products.InsertOnSubmit(newProduct);
adwContext.SubmitChanges();
// Herşey yolunda ise Transaction onaylanır
adwContext.Transaction.Commit();
}
catch
{
// Bir aksilik olduysa Transaction geri alınır
adwContext.Transaction.Rollback();
}
finally
{
if (adwContext.Connection.State == ConnectionState.Open)
adwContext.Connection.Close();
}</pre>
<p>Dikkat edileceği üzere <strong>DataContext</strong> tipinin <strong>Transaction</strong> özelliğine değer atanırken <strong>Connection</strong> üzerinden gidilmiş ve <strong>BeginTransaction</strong> metodu kullanılmıştır. BeginTransaction metodunun parametresinden yararlanılarak, Transaction' ın <strong>izolasyon seviyeside(Isolation Level) </strong>değiştirilebilir. Burada <strong>Transaction</strong>' ın oluşturulabilmesi için bağlantının(Connection) açık olması gerekmektedir. Bu sebeptende <strong>finally</strong> bloğu içerisinde açık kalan bağlantının kontrollü bir şekilde kapatılması sağlanmaktadır. Transaction' a dahil olan işlemlerin onaylanması için <strong>Commit</strong> metodu kullanılırken bir sorun ile karşılaşılması halinde o ana kadar yapılan işlemlerin geri alınması içinde <strong>Rollback</strong> metoduna başvurulmaktadır. Sonuç olarak <strong>SQL Profiler</strong> aracı izlendiğinde <strong> ekleme(insert)</strong> ve <strong>güncelleme(update)</strong> işlemlerinin yine tek bir <strong>Transaction</strong> kapsamı içerisinde ele alındığı görülmektedir.</p>
<p><strong>LINQ To SQL</strong> mimarisin şu ana kadar işlenen temel <strong>CRUD</strong> işlemlerinde ele alınması gereken başka hususlarda vardır. Söz gelimi eş zamanlı çalışan programların aynı veriler üzerinde işlemler yaptığı durumlarda oluşan <strong> çakışmaların(Conflict)</strong> ele alınması gibi. Bu ve benzeri konuları ilerleyen makalelerimizde ve görsel derslerimizde incelemeye çalışıyor olacağız. Bu makalemizde çok basit ve temel seviyede <strong>ekleme(Insert), Silme(Delete) ve Güncelleme(Update)</strong> işlemlerini nasıl yapabileceğimizi incelemeye çalıştık. Ayrıca bu işlemleri yaparken <strong> Transaction</strong>' ların nasıl kullanılabildiğinide gördük. Varsayılan olarak bilinçsiz şekilde başlatılan Transaction' ları TransactionScope ile veya Local Transaction teknikleri nasıl kontrol edebileceğimiz gördük. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://www.buraksenyurt.com/makale/images/LinqToSqlCRUD.rar">Örnek Uygulama için Tıklayın</a></p>2007-12-15T08:00:00+00:00linq to sqllinqcrudbsenyurtLanguage Integrated Query(LINQ) mimarisi özellikle programatik ortamlarda tasarlanan nesneler üzerinde, SQL cümlelerine benzer ifadeler ile sorgulamalar yapılmasına izin vermektedir. Çok doğal olarak veritabanı(database) tarafında yer alan tablo(Table), saklı yordam(Stored Procedure), görünüm(View), fonksiyon(Function) gibi unsurlarında programatik tarafta birer varlık(Entity) olarak ifade edilebilmesi, LINQ kurallarının SQL üzerindede gerçekleştirilebilmesini sağlamaktadır. Burada varlık katmanı(Entity Layer) olarakda düşünebileceğimiz yapı üzerinde yer alan nesneler, veritabanından çekilen sonuçları saklayabilmektedir. Bunun yanında programatik ortamdaki varlıklar üzerinde yeni varlık oluşturma, güncelleme, silme gibi operasyonlarda yapılabilmektedir.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=1ae26607-6671-4baa-bf94-c900a39a65ab0https://www.buraksenyurt.com/trackback.axd?id=1ae26607-6671-4baa-bf94-c900a39a65abhttps://www.buraksenyurt.com/post/LINQ-to-SQL-ile-CRUD-Islemleri-bsenyurt-com-dan#commenthttps://www.buraksenyurt.com/syndication.axd?post=1ae26607-6671-4baa-bf94-c900a39a65ab