Aracılığıyla paylaş


LINQ: .NET Language-Integrated Sorgusu

 

Don Box, Anders Hejlsberg

Şubat 2007

Aşağıdakiler cihazlar için geçerlidir:
   Visual Studio Code Adı "Orcas"
   .Net Framework 3.5

Özet: .NET Framework eklenen genel amaçlı sorgu olanakları yalnızca ilişkisel veya XML verileri için değil tüm bilgi kaynakları için geçerlidir. Bu tesise .NET Language-Integrated Sorgusu (LINQ) adı verilir. (32 yazdırılan sayfa)

İçindekiler

.NET Language-Integrated Sorgusu
Standart Sorgu İşleçlerini Kullanmaya Başlama
LINQ Projesini Destekleyen Dil Özellikleri
Diğer Standart Sorgu İşleçleri
Sorgu Söz Dizimi
LINQ to SQL: SQL Tümleştirmesi
LINQ to XML: XML Tümleştirmesi
Özet

.NET Language-Integrated Sorgusu

Yirmi yıldan sonra sektör, nesne odaklı (OO) programlama teknolojilerinin evriminde kararlı bir noktaya ulaşmıştır. Programcılar artık sınıflar, nesneler ve yöntemler gibi verilen özellikleri alır. Mevcut ve yeni nesil teknolojilere baktığımızda programlama teknolojisindeki bir sonraki büyük zorluğun, OO teknolojisi kullanılarak yerel olarak tanımlanmayan bilgilere erişim ve tümleştirme karmaşıklığını azaltmak olduğu ortaya çıktı. OO olmayan bilgilerin en yaygın iki kaynağı ilişkisel veritabanları ve XML'tir.

PROGRAMLAMA dillerimize ve çalışma zamanımıza ilişkisel veya XML'e özgü özellikler eklemek yerine, LINQ projesiyle daha genel bir yaklaşım benimsedik ve .NET Framework yalnızca ilişkisel veya XML verileri için değil, tüm bilgi kaynakları için geçerli olan genel amaçlı sorgu olanakları ekliyoruz. Bu tesise .NET Language-Integrated Sorgusu (LINQ) adı verilir.

Sorgunun geliştiricinin birincil programlama dillerinin (örneğin, Visual C#, Visual Basic) tümleşik bir özelliği olduğunu belirtmek için dille tümleşik sorgu terimini kullanırız. Dille tümleşik sorgu , sorgu ifadelerinin daha önce yalnızca kesinlik temelli kod için kullanılabilen zengin meta verilerden, derleme zamanı söz dizimi denetiminden, statik yazmadan ve IntelliSense'ten yararlanmasını sağlar. Dille tümleşik sorgu ayrıca tek bir genel amaçlı bildirim temelli sorgu olanağının yalnızca dış kaynaklardan gelen bilgilere değil tüm bellek içi bilgilere uygulanmasını sağlar.

.NET Language-Integrated Sorgusu, herhangi bir içinde dolaşma, filtreleme ve yansıtma işlemlerinin doğrudan ancak bildirim temelli bir şekilde ifade edilmesine olanak sağlayan genel amaçlı standart sorgu işleçleri kümesini tanımlar. NET tabanlı programlama dili. Standart sorgu işleçleri sorguların herhangi bir IEnumerable<T> tabanlı bilgi kaynağına uygulanmasını sağlar. LINQ, üçüncü tarafların standart sorgu işleçleri kümesini hedef etki alanına veya teknolojiye uygun yeni etki alanına özgü işleçlerle artırmasına olanak tanır. Daha da önemlisi, üçüncü taraflar standart sorgu işleçlerini uzaktan değerlendirme, sorgu çevirisi, iyileştirme gibi ek hizmetler sağlayan kendi uygulamalarıyla değiştirebilir. LINQ deseninin kurallarına bağlı kalarak, bu tür uygulamalar standart sorgu işleçleriyle aynı dil tümleştirme ve araç desteğinden faydalanır.

Sorgu mimarisinin genişletilebilirliği, HEM XML hem de SQL verileri üzerinde çalışan uygulamalar sağlamak için LINQ projesinde kullanılır. XML (LINQ to XML) üzerinden sorgu işleçleri, konak programlama dilinde XPath/XQuery işlevselliği sağlamak için verimli, kullanımı kolay, bellek içi bir XML özelliği kullanır. İlişkisel veriler (LINQ to SQL) üzerindeki sorgu işleçleri, SQL tabanlı şema tanımlarının ortak dil çalışma zamanı (CLR) türündeki sistemle tümleştirilmesini temel alır. Bu tümleştirme, ilişkisel modelin ifade gücünü ve sorgu değerlendirme performansını doğrudan temel alınan depoda korurken ilişkisel veriler üzerinde güçlü yazma sağlar.

Standart Sorgu İşleçlerini Kullanmaya Başlama

İş yerinde dille tümleşik sorguyu görmek için, bir dizinin içeriğini işlemek için standart sorgu işleçlerini kullanan basit bir C# 3.0 programıyla başlayacağız:

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

class app {
  static void Main() {
    string[] names = { "Burke", "Connor", "Frank", 
                       "Everett", "Albert", "George", 
                       "Harris", "David" };

    IEnumerable<string> query = from s in names 
                               where s.Length == 5
                               orderby s
                               select s.ToUpper();

    foreach (string item in query)
      Console.WriteLine(item);
  }
}

Bu programı derleyip çalıştırırsanız bunu çıkış olarak görürsünüz:

BURKE
DAVID
FRANK
To understand how language-integrated query works, we need to dissect the
 first statement of our program.
IEnumerable<string> query = from s in names 
                           where s.Length == 5
                           orderby s
                           select s.ToUpper();

Yerel değişken sorgusu bir sorgu ifadesiyle başlatılır. Sorgu ifadesi, standart sorgu işleçlerinden veya etki alanına özgü işleçlerden bir veya daha fazla sorgu işleci uygulayarak bir veya daha fazla bilgi kaynağı üzerinde çalışır. Bu ifadede üç standart sorgu işleci kullanılır: Where, OrderBy ve Select.

Visual Basic 9.0, LINQ'i de destekler. Visual Basic 9.0'da yazılan önceki deyim aşağıdadır:

Dim query As IEnumerable(Of String) = From s in names _
                                     Where s.Length = 5 _
                   Order By s _
                   Select s.ToUpper()

Burada gösterilen hem C# hem de Visual Basic deyimleri sorgu ifadelerini kullanır. foreach deyimi gibi sorgu ifadeleri de el ile yazabileceğiniz kod üzerinde bildirim temelli kısaltmadır. Yukarıdaki deyimler, C# dilinde gösterilen aşağıdaki açık söz dizimine benzer:

IEnumerable<string> query = names 
                            .Where(s => s.Length == 5) 
                            .OrderBy(s => s)
                            .Select(s => s.ToUpper());

Bu sorgu biçimine yöntem tabanlı sorgu adı verilir. Where, OrderBy ve Select işleçlerinin bağımsız değişkenleri, temsilcilere çok benzeyen kod parçaları olan lambda ifadeleri olarak adlandırılır. Standart sorgu işleçlerinin tek tek yöntem olarak tanımlanmasına ve nokta gösterimi kullanılarak birlikte çalıştırılmasına olanak sağlar. Bu yöntemler birlikte genişletilebilir sorgu dilinin temelini oluşturur.

LINQ Projesini Destekleyen Dil Özellikleri

LINQ, bazıları C# 3.0 ve Visual Basic 9.0'da yeni olan genel amaçlı dil özellikleri üzerine kurulmuştur. Bu özelliklerin her birinin kendi kendine yardımcı programı vardır, ancak bu özellikler topluca sorguları ve sorgulanabilir API'leri tanımlamak için genişletilebilir bir yol sağlar. Bu bölümde bu dil özelliklerini ve sorguların çok daha doğrudan ve bildirim temelli bir stiline nasıl katkıda bulunduklarını keşfedeceğiz.

Lambda İfadeleri ve İfade Ağaçları

Birçok sorgu işleci kullanıcının filtreleme, yansıtma veya anahtar ayıklama gerçekleştiren bir işlev sağlamasına olanak tanır. Sorgu tesisleri, geliştiricilere sonraki değerlendirme için bağımsız değişken olarak geçirilebilen işlevler yazmak için kullanışlı bir yol sağlayan lambda ifadeleri kavramını kullanır. Lambda ifadeleri CLR temsilcilerine benzer ve bir temsilci türü tarafından tanımlanan yöntem imzasına bağlı kalmalıdır. Bunu göstermek için, Func temsilci türünü kullanarak yukarıdaki deyimi eşdeğer ancak daha açık bir biçimde genişletebiliriz:

Func<string, bool>   filter  = s => s.Length == 5;
Func<string, string> extract = s => s;
Func<string, string> project = s => s.ToUpper();

IEnumerable<string> query = names.Where(filter) 
                                 .OrderBy(extract)
                                 .Select(project);

Lambda ifadeleri, C# 2.0'daki anonim yöntemlerin doğal evrimidir. Örneğin, aşağıdaki gibi anonim yöntemleri kullanarak önceki örneği yazabilirdik:

Func<string, bool>   filter  = delegate (string s) {
                                   return s.Length == 5; 
                               };

Func<string, string> extract = delegate (string s) { 
                                   return s; 
                               };

Func<string, string> project = delegate (string s) {
                                   return s.ToUpper(); 
                               };

IEnumerable<string> query = names.Where(filter) 
                                 .OrderBy(extract)
                                 .Select(project);

Genel olarak, geliştirici sorgu işleçleriyle adlandırılmış yöntemleri, anonim yöntemleri veya lambda ifadelerini kullanabilir. Lambda ifadeleri, yazma için en doğrudan ve kompakt söz dizimini sağlama avantajına sahiptir. Daha da önemlisi, lambda ifadeleri kod veya veri olarak derlenebilir ve bu da lambda ifadelerinin çalışma zamanında iyileştiriciler, çeviriciler ve değerlendiriciler tarafından işlenmesine olanak tanır.

System.Linq.Expressions ad alanı, ifade ağacının geleneksel IL tabanlı bir yöntem gövdesi yerine belirli bir lambda ifadesi için istendiğini belirten ayırt edici genel bir tür olan İfade<T'yi> tanımlar. İfade ağaçları, lambda ifadelerinin verimli bellek içi veri gösterimleridir ve ifadenin yapısını saydam ve açık hale getirir.

Derleyicinin yürütülebilir IL veya bir ifade ağacı yayıp yaymayacağının belirlenmesi, lambda ifadesinin nasıl kullanıldığına göre belirlenir. Türü temsilci olan bir değişkene, alana veya parametreye bir lambda ifadesi atandığında, derleyici anonim yönteminkiyle aynı IL'yi yayar. Bir temsilci türü T için türü İfade<T> olan bir değişkene, alana veya parametreye lambda ifadesi atandığında, derleyici bunun yerine bir ifade ağacı yayar.

Örneğin, aşağıdaki iki değişken bildirimini göz önünde bulundurun:

Func<int, bool>             f = n => n < 5;
Expression<Func<int, bool>> e = n => n < 5;

f değişkeni, doğrudan yürütülebilir bir temsilciye başvurudur:

bool isSmall = f(2); // isSmall is now true

e değişkeni, doğrudan yürütülebilir olmayan bir ifade ağacına başvurudur:

bool isSmall = e(2); // compile error, expressions == data

Etkili bir şekilde opak kod olan temsilcilerden farklı olarak, programımızdaki diğer veri yapılarında olduğu gibi ifade ağacıyla etkileşim kurabiliriz.

Expression<Func<int, bool>> filter = n => n < 5;

BinaryExpression    body  = (BinaryExpression)filter.Body;
ParameterExpression left  = (ParameterExpression)body.Left;
ConstantExpression  right = (ConstantExpression)body.Right;

Console.WriteLine("{0} {1} {2}", 
                  left.Name, body.NodeType, right.Value);

Yukarıdaki örnek çalışma zamanında ifade ağacını ayrıştırır ve aşağıdaki dizeyi yazdırır:

n LessThan 5

İfadeleri çalışma zamanında veri olarak ele alma özelliği, platformun parçası olan temel sorgu soyutlamalarından yararlanan üçüncü taraf kitaplık ekosistemini etkinleştirmek için kritik öneme sahiptir. LINQ to SQL veri erişimi uygulaması, ifade ağaçlarını depoda değerlendirmeye uygun T-SQL deyimlerine çevirmek için bu tesisten yararlanıyor.

Uzantı Metotları

Lambda ifadeleri sorgu mimarisinin önemli parçalarından biridir. Uzantı yöntemleri başka bir yöntemdir. Uzantı yöntemleri, dinamik dillerde popüler hale gelen "ördek yazma" esnekliğini statik olarak yazılan dillerin performansı ve derleme zamanı doğrulamasıyla birleştirir. Genişletme yöntemleriyle, üçüncü taraflar bir türün genel sözleşmesini yeni yöntemlerle genişletirken, tek tek tür yazarlarının bu yöntemleri kendi özel uygulamalarını sağlamalarına izin verebilir.

Uzantı yöntemleri statik sınıflarda statik yöntemler olarak tanımlanır, ancak CLR meta verilerinde [System.Runtime.CompilerServices.Extension] özniteliğiyle işaretlenir. Dillerin, uzantı yöntemleri için doğrudan söz dizimi sağlaması teşvik edilir. C# dilinde uzantı yöntemleri, uzantı yönteminin ilk parametresine uygulanması gereken bu değiştirici ile gösterilir. Şimdi en basit sorgu işlecinin tanımına bakalım: Burada:

namespace System.Linq {
  using System;
  using System.Collections.Generic;

  public static class Enumerable {
    public static IEnumerable<T> Where<T>(
             this IEnumerable<T> source,
             Func<T, bool> predicate) {

      foreach (T item in source)
        if (predicate(item))
          yield return item;
    }
  }
}

Uzantı yönteminin ilk parametresinin türü, uzantının hangi tür için geçerli olduğunu gösterir. Yukarıdaki örnekte, Where uzantısı yöntemi IEnumerable<T> türünü genişletir. Where statik bir yöntem olduğundan, bunu diğer statik yöntemler gibi doğrudan çağırabiliriz:

IEnumerable<string> query = Enumerable.Where(names, 
                                          s => s.Length < 6);

Ancak, uzantı yöntemlerini benzersiz kılan şey, örnek söz dizimi kullanılarak da çağrılabilmesidir:

IEnumerable<string> query = names.Where(s => s.Length < 6);

Uzantı yöntemleri, kapsamdaki uzantı yöntemlerine göre derleme zamanında çözümlenir. Bir ad alanı C# dilinde bir using deyimiyle veya Visual Basic'te import deyimiyle içeri aktarıldığında, bu ad alanından statik sınıflar tarafından tanımlanan tüm uzantı yöntemleri kapsama alınır.

Standart sorgu işleçleri System.Linq.Enumerable türünde uzantı yöntemleri olarak tanımlanır. Standart sorgu işleçlerini incelerken, bunlardan birkaçı dışında tümünün IEnumerable<T> arabirimi açısından tanımlandığını fark edeceksiniz. Bu, her IEnumerable<T> uyumlu bilgi kaynağının C# dilinde aşağıdaki using deyimini ekleyerek standart sorgu işleçlerini aldığı anlamına gelir:

using System.Linq; // makes query operators visible

Belirli bir tür için standart sorgu işleçlerini değiştirmek isteyen kullanıcılar: uyumlu imzalarla belirli bir türde kendi aynı adlı yöntemlerini tanımlayabilir veya belirli türü genişleten yeni aynı adlı uzantı yöntemlerini tanımlayabilir. Standart sorgu işleçlerini tamamen zamanlamak isteyen kullanıcılar, System.Linq'i kapsama alıp IEnumerable<T> için kendi uzantı yöntemlerini yazamaz.

Uzantı yöntemlerine çözünürlük açısından en düşük öncelik verilir ve yalnızca hedef türü ve temel türleri üzerinde uygun bir eşleşme olmadığında kullanılır. Bu, kullanıcı tanımlı türlerin standart işleçlere göre öncelikli olan kendi sorgu işleçlerini sağlamasına olanak tanır. Örneğin, aşağıdaki özel koleksiyonu göz önünde bulundurun:

public class MySequence : IEnumerable<int> {
  public IEnumerator<int> GetEnumerator() {
    for (int i = 1; i <= 10; i++) 
      yield return i; 
  }

  IEnumerator IEnumerable.GetEnumerator() {
    return GetEnumerator(); 
  }

  public IEnumerable<int> Where(Func<int, bool> filter) {
    for (int i = 1; i <= 10; i++) 
      if (filter(i)) 
        yield return i;
  }
}

Bu sınıf tanımı göz önünde bulundurulduğunda, örnek yöntemleri uzantı yöntemlerinden öncelikli olduğundan aşağıdaki program uzantı yöntemini değil MySequence.Where uygulamasını kullanır:

MySequence s = new MySequence();
foreach (int item in s.Where(n => n > 3))
    Console.WriteLine(item);

OfType işleci, IEnumerable<T> tabanlı bilgi kaynağını genişletmeyen birkaç standart sorgu işlecinden biridir. Şimdi OfType sorgu işlecine bakalım:

public static IEnumerable<T> OfType<T>(this IEnumerable source) {
  foreach (object item in source) 
    if (item is T) 
      yield return (T)item;
}

OfType yalnızca IEnumerable<T> tabanlı kaynakları değil, aynı zamanda .NET Framework sürüm 1.0'da bulunan parametrelenmemiş IEnumerable arabirimine karşı yazılan kaynakları kabul eder. OfType işleci, kullanıcıların standart sorgu işleçlerini aşağıdaki gibi klasik .NET koleksiyonlarına uygulamasına olanak tanır:

// "classic" cannot be used directly with query operators
IEnumerable classic = new OlderCollectionType();

// "modern" can be used directly with query operators
IEnumerable<object> modern = classic.OfType<object>();

Bu örnekte değişken modern , klasik değerlerle aynı değer dizisini verir. Ancak türü, standart sorgu işleçleri de dahil olmak üzere modern IEnumerable<T> koduyla uyumludur.

OfType işleci, türe göre bir kaynaktan değerleri filtrelemeye izin verdiğinden daha yeni bilgi kaynakları için de kullanışlıdır. Yeni diziyi oluştururken OfType , özgün dizinin tür bağımsız değişkeniyle uyumlu olmayan üyelerini atlar. Heterojen bir diziden dize ayıklayan bu basit programı göz önünde bulundurun:

object[] vals = { 1, "Hello", true, "World", 9.1 };
IEnumerable<string> justStrings = vals.OfType<string>();

foreach deyiminde justStrings değişkenini numaralandırdığımızda iki dizeden oluşan bir dizi alacağız: "Hello" ve "World."

Ertelenen Sorgu Değerlendirmesi

Gözlem okuyucular, standart Where işlecinin C# 2.0'da tanıtılan verim yapısı kullanılarak uygulandığını belirtmiş olabilir. Bu uygulama tekniği, değer dizilerini döndüren tüm standart işleçler için ortaktır. Verim kullanımının ilginç bir avantajı vardır; bu da sorgunun bir foreach deyimiyle veya temel alınan GetEnumerator ve MoveNext yöntemlerini el ile kullanarak yinelenene kadar gerçekten değerlendirilmemesidir. Bu ertelenmiş değerlendirme, sorguların her seferinde potansiyel olarak farklı sonuçlar veren birden çok kez değerlendirilebilen IEnumerable<T> tabanlı değerler olarak tutulmasını sağlar.

Birçok uygulama için tam olarak istenen davranış budur. Sorgu değerlendirmesinin sonuçlarını önbelleğe almak isteyen uygulamalar için, sorgunun hemen değerlendirilmesini zorlayan ve sorgu değerlendirmesinin sonuçlarını içeren bir List<T> veya dizi döndüren iki işleç (ToList ve ToArray) sağlanır.

Ertelenmiş sorgu değerlendirmesinin nasıl çalıştığını görmek için bir dizi üzerinde basit bir sorgu çalıştıran bu programı göz önünde bulundurun:

// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };

// declare a variable that represents a query
IEnumerable<string> ayes = names.Where(s => s[0] == 'A');

// evaluate the query
foreach (string item in ayes) 
  Console.WriteLine(item);

// modify the original information source
names[0] = "Bob";

// evaluate the query again, this time no "Allen"
foreach (string item in ayes) 
    Console.WriteLine(item);

Ayes değişkeni her yinelendiğinde sorgu değerlendirilir. Sonuçların önbelleğe alınmış bir kopyasının gerekli olduğunu belirtmek için, sorguya aşağıdaki gibi bir ToList veya ToArray işleci ekleyebiliriz:

// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };

// declare a variable that represents the result
// of an immediate query evaluation
string[] ayes = names.Where(s => s[0] == 'A').ToArray();

// iterate over the cached query results
foreach (string item in ayes) 
    Console.WriteLine(item);

// modifying the original source has no effect on ayes
names[0] = "Bob";

// iterate over result again, which still contains "Allen"
foreach (string item in ayes)
    Console.WriteLine(item);

Hem ToArray hem de ToList anında sorgu değerlendirmeye zorlar. Aynı durum tekil değerler döndüren standart sorgu işleçleri için de geçerlidir (örneğin: First, ElementAt, Sum, Average, All, Any).

IQueryable<T> Arabirimi

Aynı ertelenmiş yürütme modeli genellikle LINQ to SQL gibi ifade ağaçlarını kullanarak sorgu işlevselliğini uygulayan veri kaynakları için istenir. Bu veri kaynakları, LINQ deseni için gereken tüm sorgu işleçlerinin ifade ağaçları kullanılarak uygulandığı IQueryable<T> arabirimini uygulamadan yararlanabilir. Her IQueryable<T> , ifade ağacı biçiminde "sorguyu çalıştırmak için gereken kodun" bir gösterimine sahiptir. Tüm ertelenen sorgu işleçleri, bu ifade ağacını bu sorgu işlecine yapılan bir çağrının gösterimiyle genişleten yeni bir IQueryable<T> döndürür. Bu nedenle, sorguyu değerlendirme zamanı geldiğinde, genellikle IQueryable<T> numaralandırıldığında, veri kaynağı sorgunun tamamını temsil eden ifade ağacını tek bir toplu işlemde işleyebilir. Örneğin, sorgu işleçlerine yönelik çok sayıda çağrı tarafından alınan karmaşık bir LINQ to SQL sorgusu veritabanına yalnızca tek bir SQL sorgusu gönderilmesine neden olabilir.

IQueryable<T> arabirimini uygulayarak bu erteleme işlevini yeniden kullanmanın veri kaynağı uygulayıcılarının avantajı açıktır. Öte yandan, sorguları yazan istemciler için, uzak bilgi kaynakları için ortak bir türe sahip olmak büyük bir avantajdır. Yalnızca farklı veri kaynaklarına karşı kullanılabilecek çok biçimli sorgular yazmalarına izin vermekle kalmaz, aynı zamanda etki alanlarından geçen sorgular yazma olasılığını da açar.

Bileşik Değerleri Başlatma

Lambda ifadeleri ve uzantı yöntemleri, yalnızca bir değer dizisinin üyelerini filtreleyen sorgular için ihtiyacımız olan her şeyi sağlar. Çoğu sorgu ifadesi de bu üyeler üzerinde yansıtma gerçekleştirerek özgün dizinin üyelerini, değeri ve türü özgünden farklı olabilecek üyelere dönüştürür. LINQ, bu dönüşümlerin yazmasını desteklemek için, yapılandırılmış türlerin yeni örneklerini oluşturmak için nesne başlatıcıları adlı yeni bir yapıya dayanır. Bu belgenin geri kalanında aşağıdaki türün tanımlandığını varsayacağız:

public class Person {
  string name;
  int age;
  bool canCode;

  public string Name {
    get { return name; } set { name = value; }
  }

  public int Age {
    get { return age; } set { age = value; }
  }

  public bool CanCode {
    get { return canCode; } set { canCode = value; }
  }
}

Nesne başlatıcılar, genel alanlara ve bir türün özelliklerine göre kolayca değer oluşturmamıza olanak sağlar. Örneğin, Kişi türünde yeni bir değer oluşturmak için şu deyimi yazabiliriz:

Person value = new Person {
    Name = "Chris Smith", Age = 31, CanCode = false
};

Bu deyim, sekans olarak aşağıdaki deyim dizisiyle eşdeğerdir:

Person value = new Person();
value.Name = "Chris Smith";
value.Age = 31;
value.CanCode = false;

Nesne başlatıcılar, yalnızca ifadelere izin verilen bağlamlarda (lambda ifadeleri ve ifade ağaçları gibi) yeni yapılandırılmış değerlerin oluşturulmasına izin verdikleri için dille tümleşik sorgu için önemli bir özelliktir. Örneğin, giriş dizisindeki her değer için yeni bir Kişi değeri oluşturan bu sorgu ifadesini göz önünde bulundurun:

IEnumerable<Person> query = names.Select(s => new Person {
    Name = s, Age = 21, CanCode = s.Length == 5
});

Nesne başlatma söz dizimi, yapılandırılmış değer dizilerini başlatmak için de uygundur. Örneğin, tek tek nesne başlatıcıları kullanılarak başlatılan bu dizi değişkenlerini göz önünde bulundurun:

static Person[] people = {
  new Person { Name="Allen Frances", Age=11, CanCode=false },
  new Person { Name="Burke Madison", Age=50, CanCode=true },
  new Person { Name="Connor Morgan", Age=59, CanCode=false },
  new Person { Name="David Charles", Age=33, CanCode=true },
  new Person { Name="Everett Frank", Age=16, CanCode=true },
};

Yapılandırılmış Değerler ve Türler

LINQ projesi, bazı türlerin hem durum hem de davranışa sahip tam kapsamlı bir nesne yerine yapılandırılmış bir değer üzerinde statik bir "şekil" sağlamak için mevcut olduğu veri odaklı programlama stilini destekler. Bu şirket içi mantıksal sonuca varılırsa, genellikle geliştiricinin önem verdiği tek şey değerin yapısıdır ve bu şekil için adlandırılmış türe duyulan ihtiyaç çok az kullanılır. Bu, yeni yapıların başlatmalarıyla "satır içi" tanımlanmasına olanak sağlayan anonim türlerin kullanıma sunulmasına yol açar.

C# dilinde, anonim türlerin söz dizimi nesne başlatma söz dizimine benzer, ancak türün adı atlanır. Örneğin, aşağıdaki iki deyimi göz önünde bulundurun:

object v1 = new Person {
    Name = "Brian Smith", Age = 31, CanCode = false
};

object v2 = new { // note the omission of type name
    Name = "Brian Smith", Age = 31, CanCode = false
};

v1 ve v2 değişkenlerinin her ikisi de CLR türü Name, Age ve CanCode olmak üzere üç genel özelliğe sahip bellek içi bir nesneye işaret eder. Değişkenler , v2'deanonim bir türün örneğine başvuruda bulunan farklılık gösterir. CLR açısından, anonim türler diğer türlerden farklı değildir. Anonim türleri özel kılan, bunların programlama dilinizde anlamlı bir adı olmamasıdır. Anonim tür örnekleri oluşturmanın tek yolu, yukarıda gösterilen söz dizimini kullanmaktır.

Değişkenlerin anonim türlerin örneklerine başvurmasına izin vermek ancak statik yazmadan yararlanmaya devam etmek için, C# örtük olarak yazılan yerel değişkenleri kullanıma sunar:Var anahtar sözcüğü, yerel değişken bildirimleri için tür adı yerine kullanılabilir. Örneğin, bu yasal C# 3.0 programını göz önünde bulundurun:

var s = "Bob";
var n = 32;
var b = true;

var anahtar sözcüğü, derleyiciye değişkeni başlatmak için kullanılan ifadenin statik türünden değişkenin türünü çıkarmasını söyler. Bu örnekte s, n ve b türleri sırasıyla dize, int ve bool'dır. Bu program aşağıdakiyle aynıdır:

string s = "Bob";
int    n = 32;
bool   b = true;

var anahtar sözcüğü, türleri anlamlı adlara sahip olan değişkenler için kolaylık sağlar, ancak anonim tür örneklerine başvuran değişkenler için bir zorunluluktur.

var value = new { 
  Name = " Brian Smith", Age = 31, CanCode = false
};

Yukarıdaki örnekte değişken değeri , tanımı aşağıdaki sözde C# ile eşdeğer olan anonim bir türdür:

internal class ??? {
  string _Name;
  int    _Age;
  bool   _CanCode;

  public string Name { 
    get { return _Name; } set { _Name = value; }
  }

  public int Age{ 
    get { return _Age; } set { _Age = value; }
  }

  public bool CanCode { 
    get { return _CanCode; } set { _CanCode = value; }
  }

  public bool Equals(object obj) { ... }

  public bool GetHashCode() { ... }
}

Anonim türler derleme sınırları arasında paylaşılamaz; ancak derleyici, her derlemedeki belirli bir özellik adı/tür çiftleri dizisi için en fazla bir anonim tür olmasını sağlar.

Anonim türler genellikle mevcut bir yapılandırılmış değerin bir veya daha fazla üyesini seçmek için projeksiyonlarda kullanıldığından, anonim türün başlatılmasında başka bir değerdeki alanlara veya özelliklere başvurabiliriz. Bu, adı, türü ve değeri başvuruda bulunılan özellik veya alandan kopyalanan bir özelliğin yeni anonim türüyle sonuçlanır.

Örneğin, diğer değerlerden özellikleri birleştirerek yeni bir yapılandırılmış değer oluşturan bu örneği göz önünde bulundurun:

var bob = new Person { Name = "Bob", Age = 51, CanCode = true };
var jane = new { Age = 29, FirstName = "Jane" };

var couple = new {
    Husband = new { bob.Name, bob.Age },
    Wife = new { Name = jane.FirstName, jane.Age }
};

int    ha = couple.Husband.Age; // ha == 51
string wn = couple.Wife.Name;   // wn == "Jane"

Yukarıda gösterilen alanlara veya özelliklere başvurmak, aşağıdaki daha açık formu yazmak için kullanışlı bir söz dizimidir:

var couple = new {
    Husband = new { Name = bob.Name, Age = bob.Age },
    Wife = new { Name = jane.FirstName, Age = jane.Age }
};

Her iki durumda da çift değişkeni bob vejane'denName ve Age özelliklerinin kendi kopyasını alır.

Anonim türler genellikle sorgunun select yan tümcesinde kullanılır. Örneğin, aşağıdaki sorguyu göz önüne alın:

var query = people.Select(p => new { 
               p.Name, BadCoder = p.Age == 11
           });

foreach (var item in query) 
  Console.WriteLine("{0} is a {1} coder", 
                     item.Name,
                     item.BadCoder ? "bad" : "good");

Bu örnekte, kişi türü üzerinde, işleme kodumuz için ihtiyacımız olan şekille tam olarak eşleşen ancak yine de statik türün avantajlarını sağlayan yeni bir projeksiyon oluşturabildik.

Diğer Standart Sorgu İşleçleri

Yukarıda açıklanan temel sorgu olanaklarına ek olarak, bir dizi işleç sıraları işlemenin ve sorgu oluşturmanın yararlı yollarını sağlar ve kullanıcıya standart sorgu işleçlerinin uygun çerçevesi içinde sonuç üzerinde yüksek düzeyde denetim sağlar.

Sıralama ve Gruplandırma

Genel olarak, bir sorgunun değerlendirilmesi, temel alınan bilgi kaynaklarında içsel olan bir sırada üretilen bir dizi değerle sonuçlanır. Geliştiricilere bu değerlerin üretildiği sıra üzerinde açık denetim sağlamak için, standart sorgu işleçleri sırayı denetlemek için tanımlanır. Bu işleçlerden en temeli OrderBy işlecidir.

OrderBy ve OrderByDescending işleçleri herhangi bir bilgi kaynağına uygulanabilir ve kullanıcının sonuçları sıralamak için kullanılan değeri üreten bir anahtar ayıklama işlevi sağlamasına olanak tanır. OrderBy ve OrderByDescending, anahtarlar üzerinde kısmi bir sıra uygulamak için kullanılabilecek isteğe bağlı bir karşılaştırma işlevini de kabul eder. Şimdi temel bir örneğe bakalım:

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

// unity sort
var s1 = names.OrderBy(s => s); 
var s2 = names.OrderByDescending(s => s);

// sort by length
var s3 = names.OrderBy(s => s.Length); 
var s4 = names.OrderByDescending(s => s.Length);

İlk iki sorgu ifadesi, kaynağın üyelerini dize karşılaştırması temelinde sıralamayı temel alan yeni diziler oluşturur. İkinci iki sorgu, kaynağın üyelerini her dizenin uzunluğuna göre sıralamayı temel alan yeni diziler oluşturur.

Birden çok sıralama ölçütüne izin vermek için hem OrderBy hem de OrderByDescending, genel IEnumerable<T yerine OrderedSequence T> döndürür.<> Ek (alt) sıralama ölçütü uygulayan ThenBy veThenByDescending adlı iki işleç yalnızca OrderedSequence<T> üzerinde tanımlanır. ThenBy/ThenByDescending kendileri OrderedSequence<T> döndürerek herhangi bir sayıda ThenBy/ThenByDescending işlecinin uygulanmasına izin verir:

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

var s1 = names.OrderBy(s => s.Length).ThenBy(s => s);

Bu örnekte s1 tarafından başvuruda bulunan sorgunun değerlendirilmesi aşağıdaki değer dizisini verir:

"Burke", "David", "Frank", 
"Albert", "Connor", "George", "Harris", 
"Everett"

OrderBy işleç ailesine ek olarak, standart sorgu işleçleri bir Reverse işleci de içerir. Ters yalnızca bir sıra üzerinde numaralandırır ve aynı değerleri ters sırada verir. OrderBy'nin aksine Reverse, sırayı belirlerken gerçek değerleri dikkate almaz, bunun yerine yalnızca değerlerin temel alınan kaynak tarafından üretiliş sırasına bağlıdır.

OrderBy işleci, bir dizi değere sıralama düzeni uygular. Standart sorgu işleçleri, anahtar ayıklama işlevini temel alan bir dizi değer üzerinde bölümleme uygulayan GroupBy işlecini de içerir. GroupBy işleci, karşılaşılan her ayrı anahtar değeri için bir IGrouping değeri dizisi döndürür. IGrouping, içeriğini ayıklamak için kullanılan anahtarı da içeren bir IEnumerable'dır:

public interface IGrouping<K, T> : IEnumerable<T> {
  public K Key { get; }
}

GroupBy'nin en basit uygulaması şöyle görünür:

string[] names = { "Albert", "Burke", "Connor", "David",
                   "Everett", "Frank", "George", "Harris"};

// group by length
var groups = names.GroupBy(s => s.Length);

foreach (IGrouping<int, string> group in groups) {
    Console.WriteLine("Strings of length {0}", group.Key);

    foreach (string value in group)
        Console.WriteLine("  {0}", value);
}    

Çalıştırıldığında, bu program aşağıdakileri yazdırır:

Strings of length 6
  Albert
  Connor
  George
  Harris
Strings of length 5
  Burke
  David
  Frank
Strings of length 7
  Everett

A la Select, GroupBy , grupların üyelerini doldurmak için kullanılan bir projeksiyon işlevi sağlamanıza olanak tanır.

string[] names = { "Albert", "Burke", "Connor", "David",
                   "Everett", "Frank", "George", "Harris"};

// group by length
var groups = names.GroupBy(s => s.Length, s => s[0]);
foreach (IGrouping<int, char> group in groups) {
    Console.WriteLine("Strings of length {0}", group.Key);

    foreach (char value in group)
        Console.WriteLine("  {0}", value);
}  

Bu çeşitleme aşağıdakileri yazdırır:

Strings of length 6
  A
  C
  G
  H
Strings of length 5
  B
  D
  F
Strings of length 7
  E

Not Bu örnekten, öngörülen türün kaynakla aynı olması gerekmez. Bu durumda, bir dizi dizeden karakterlere bir tamsayı grubu oluşturduk.

Toplama İşleçleri

Bir değer dizisini tek bir değere toplamaya yönelik birkaç standart sorgu işleci tanımlanır. En genel toplama işleci Toplama'dır ve şu şekilde tanımlanır:

public static U Aggregate<T, U>(this IEnumerable<T> source, 
                                U seed, Func<U, T, U> func) {
  U result = seed;

  foreach (T element in source) 
      result = func(result, element);

  return result;
}

Toplama işleci, bir değer dizisi üzerinde hesaplama gerçekleştirmeyi basitleştirir. Toplama , temel alınan dizinin her üyesi için lambda ifadesini bir kez çağırarak çalışır. Aggregate lambda ifadesini her çağırışında, hem dizideki üyeyi hem de bir toplanmış değeri geçirir (ilk değer, Toplamaya yönelik çekirdek parametresidir). Lambda ifadesinin sonucu önceki toplanan değerin yerini alır ve Toplama , lambda ifadesinin nihai sonucunu döndürür.

Örneğin, bu program toplam karakter sayısını bir dize dizisi üzerinde toplamak için Toplama'yı kullanır:

string[] names = { "Albert", "Burke", "Connor", "David",
                   "Everett", "Frank", "George", "Harris"};

int count = names.Aggregate(0, (c, s) => c + s.Length);
// count == 46

Genel amaçlı Toplama işlecine ek olarak, standart sorgu işleçleri de genel amaçlı Count işlecini ve bu yaygın toplama işlemlerini basitleştiren dört sayısal toplama işlecini (Min, Max, Sum ve Average) içerir. Sayısal toplama işlevleri, dizi üyelerini sayısal bir türe dönüştüren bir işlev sağlandığı sürece sayısal tür dizileri (örneğin, int, çift, ondalık) veya rastgele değer dizileri üzerinde çalışır.

Bu program, az önce açıklanan Sum işlecinin her iki türünü de gösterir:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
string[] names = { "Albert", "Burke", "Connor", "David",
                   "Everett", "Frank", "George", "Harris"};

int total1 = numbers.Sum();            // total1 == 55
int total2 = names.Sum(s => s.Length); // total2 == 46

Not İkinci Sum deyimi, Toplama kullanan önceki örne eşdeğerdir.

Select vs. SelectMany

Select işleci, transform işlevinin kaynak dizideki her değer için bir değer üretmesini gerektirir. Dönüştürme işleviniz kendisi bir dizi olan bir değer döndürürse, alt dizileri el ile çapraz geçiş yapmak tüketiciye bağlıdır. Örneğin, var olan String.Split yöntemini kullanarak dizeleri belirteçlere bölen bu programı göz önünde bulundurun:

string[] text = { "Albert was here", 
                  "Burke slept late", 
                  "Connor is happy" };

var tokens = text.Select(s => s.Split(' '));

foreach (string[] line in tokens)
    foreach (string token in line)
        Console.Write("{0}.", token);

Bu program çalıştırıldığında aşağıdaki metni yazdırır:

Albert.was.here.Burke.slept.late.Connor.is.happy.

İdeal olarak sorgumuzun birleştirilmiş belirteç dizisi döndürmesini ve ara dizeyi[] tüketiciye kullanıma vermemiş olmasını isterdik. Bunu başarmak için Select işleci yerine SelectMany işlecini kullanırız. SelectMany işleci, Select işlecine benzer şekilde çalışır. Dönüştürme işlevinin daha sonra SelectMany işleci tarafından genişletilmiş bir dizi döndürmesi beklenir. SelectMany kullanılarak yeniden yazılan programımız aşağıdadır:

string[] text = { "Albert was here", 
                  "Burke slept late", 
                  "Connor is happy" };

var tokens = text.SelectMany(s => s.Split(' '));

foreach (string token in tokens)
    Console.Write("{0}.", token);

SelectMany kullanımı, her ara sıranın normal değerlendirmenin bir parçası olarak genişletilmesine neden olur.

SelectMany , iki bilgi kaynağını birleştirmek için idealdir:

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

var query = names.SelectMany(n => 
                     people.Where(p => n.Equals(p.Name))
                 );

SelectMany'ye geçirilen lambda ifadesinde, iç içe sorgu farklı bir kaynağa uygulanır, ancak dış kaynaktan geçirilen parametre kapsamındadır n. Böylece insanlar. Burada her n için bir kez çağrılır ve sonuçta elde edilen diziler son çıkış için SelectMany tarafından düzleştirilmiştir. Sonuç, ad dizisinde adı görünen tüm kişilerin dizisidir.

Birleşim İşleçleri

Nesne odaklı bir programda, birbiriyle ilişkili nesneler genellikle kolayca gezinilebilen nesne başvuruları ile ilişkilendirilir. Aynı durum genellikle veri girişlerinin sembolik olarak birbirlerine "işaret etmek" dışında bir seçeneği olmayan dış bilgi kaynakları için de geçerli değildir ve kimlikler veya varlığı benzersiz olarak tanımlayabilen diğer veriler kullanılır. Birleştirme kavramı, bir dizinin öğelerini başka bir diziden "eşleştirdikleri" öğelerle bir araya getirme işlemini ifade eder.

SelectMany'nin önceki örneği aslında tam olarak bunu yapar ve dizeleri adları bu dizeler olan kişilerle eşleştirmektedir. Ancak, bu özel amaç için SelectMany yaklaşımı çok verimli değildir; adların her bir öğesi için kişilerin tüm öğelerinde döngü oluşturur. Bu senaryonun tüm bilgilerini (iki bilgi kaynağı ve eşleştirildiği "anahtarlar") tek bir yöntem çağrısında bir araya getirerek Join işleci çok daha iyi bir iş yapabilir:

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

var query = names.Join(people, n => n, p => p.Name, (n,p) => p);

Bu biraz ağız dolusu bir işlemdir, ancak parçaların birbirine nasıl uyacağını görün: Join yöntemi "dış" veri kaynağında , adlarda çağrılır. İlk bağımsız değişken "iç" veri kaynağıdır, kişiler. İkinci ve üçüncü bağımsız değişkenler, sırasıyla dış ve iç kaynakların öğelerinden anahtarları ayıklamak için kullanılan lambda ifadeleridir. Bu anahtarlar Join yönteminin öğeleri eşleştirmek için kullandığı anahtarlardır. Burada adların kendilerinin kişilerin Name özelliğiyle eşleşmesini istiyoruz. Son lambda ifadesi daha sonra sonuçta elde edilen dizinin öğelerini oluşturmakla sorumludur: N vep eşleşen her öğe çiftiyle birlikte çağrılır ve sonucu şekillendirmek için kullanılır. Bu durumda n'yi atıp p'yi döndürmeyi seçiyoruz. Sonuç, Adıad listesinde olan kişilerinKişi öğelerinin listesidir.

Join'in daha güçlü bir kuzeni GroupJoin işlecidir. GroupJoin , sonuç şekillendirme lambda ifadesinin kullanılma biçiminde Join'ten farklıdır: Her bir dış ve iç öğe çiftiyle çağrılması yerine, her dış öğe için yalnızca bir kez çağrılır ve bu dış öğeyle eşleşen tüm iç öğelerin sırası kullanılır. Bunu somut yapmak için:

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

var query = names.GroupJoin(people, n => n, p => p.Name,                   
                 (n, matching) => 
                      new { Name = n, Count = matching.Count() }
);

Bu çağrı, bu ada sahip olan kişi sayısıyla eşleştirilmiş olarak başladığınız adların bir dizisini oluşturur. Bu nedenle GroupJoin işleci, sonuçlarınızı dış öğe için "eşleşme kümesinin" tamamına dayandırmanıza olanak tanır.

Sorgu Söz Dizimi

C# dilindeki mevcut foreach deyimi, .NET Frameworks IEnumerable/IEnumerator yöntemleri üzerinde yineleme için bildirim temelli bir söz dizimi sağlar. foreach deyimi kesinlikle isteğe bağlıdır, ancak çok kullanışlı ve popüler bir dil mekanizması olduğu kanıtlanmıştır.

Bu örneği temel alan sorgu ifadeleri , sorguları en yaygın sorgu işleçleri için bildirim temelli söz dizimi ile basitleştirir: Where, Join, GroupJoin, Select, SelectMany, GroupBy, OrderBy, ThenBy, OrderByDescending, ThenByDescending ve Cast.

İlk olarak şu incelemeye başladığımız basit sorguya bakalım:

IEnumerable<string> query = names 
                            .Where(s => s.Length == 5) 
                            .OrderBy(s => s)
                            .Select(s => s.ToUpper());

Sorgu ifadesi kullanarak tam olarak şu ifadeyi yeniden yazabiliriz:

IEnumerable<string> query = from s in names 
                            where s.Length == 5
                            orderby s
                            select s.ToUpper();

C# dilindeki foreach deyimi gibi sorgu ifadeleri de daha kompakt ve daha kolay okunur ancak tamamen isteğe bağlıdır. Sorgu ifadesi olarak yazılabilir her ifadenin, noktalı gösterimi kullanan karşılık gelen (daha ayrıntılı olsa da) söz dizimi vardır.

Bir sorgu ifadesinin temel yapısına bakarak başlayalım. C# dilindeki her söz dizimsel sorgu ifadesi from yan tümcesiyle başlar ve select veya group yan tümcesiyle biter. from yan tümcesinin ardından sıfır veya daha fazla from, let, where, join ve orderby yan tümceleri gelebilir. Her from yan tümcesi, bir dizi üzerinde bir aralık değişkeni sunan bir oluşturucudur; her let yan tümcesi bir ifadenin sonucuna bir ad verir; ve her where yan tümcesi, öğeleri sonuçtan dışlayan bir filtredir. Her join yan tümcesi, yeni bir veri kaynağını önceki yan tümcelerin sonuçlarıyla ilişkilendirmektedir. Orderby yan tümcesi, sonuç için bir sıralama belirtir:

query-expression ::= from-clause query-body

query-body ::= 

      query-body-clause* final-query-clause query-continuation?

query-body-clause ::=
 (from-clause 
      | join-clause 
      | let-clause 
      | where-clause 
      | orderby-clause)

from-clause ::=from itemName in srcExpr

join-clause ::=join itemName in srcExpr on keyExpr equals keyExpr 
       (into itemName)?

let-clause ::=let itemName = selExpr

where-clause ::= where predExpr

orderby-clause ::= orderby (keyExpr (ascending | descending)?)*

final-query-clause ::=
 (select-clause | groupby-clause)

select-clause ::= select selExpr

groupby-clause ::= group selExpr by keyExprquery-continuation ::= intoitemName query-body

Örneğin, şu iki sorgu ifadesini göz önünde bulundurun:

var query1 = from p in people
             where p.Age > 20
             orderby p.Age descending, p.Name
             select new { 
                 p.Name, Senior = p.Age > 30, p.CanCode
             };

var query2 = from p in people
             where p.Age > 20
             orderby p.Age descending, p.Name
             group new { 
                p.Name, Senior = p.Age > 30, p.CanCode
             } by p.CanCode;

Derleyici, bu sorgu ifadelerini aşağıdaki açık nokta gösterimi kullanılarak yazılmış gibi değerlendirir:

var query1 = people.Where(p => p.Age > 20)
                   .OrderByDescending(p => p.Age)
                   .ThenBy(p => p.Name)
                   .Select(p => new { 
                       p.Name, 
                       Senior = p.Age > 30, 
                       p.CanCode
                   });

var query2 = people.Where(p => p.Age > 20)
                   .OrderByDescending(p => p.Age)
                   .ThenBy(p => p.Name)
                   .GroupBy(p => p.CanCode, 
                            p => new {
                                   p.Name, 
                                   Senior = p.Age > 30, 
                                   p.CanCode
                   });

Sorgu ifadeleri, belirli adlara sahip yöntemlerin çağrılarına mekanik bir çeviriden geçer. Bu nedenle seçilen tam sorgu işleci uygulaması hem sorgulanan değişkenlerin türüne hem de kapsamdaki uzantı yöntemlerine bağlıdır.

Şimdiye kadar gösterilen sorgu ifadeleri yalnızca bir oluşturucu kullandı. Birden fazla oluşturucu kullanıldığında, izleyen her oluşturucu öncül bağlamında değerlendirilir. Örneğin, sorgumuzda bu küçük değişikliği göz önünde bulundurun:

var query = from s1 in names 
            where s1.Length == 5
            from s2 in names 
            where s1 == s2
            select s1 + " " + s2;

Bu giriş dizisinde çalıştırıldığında:

string[] names = { "Burke", "Connor", "Frank", "Everett", 
                   "Albert", "George", "Harris", "David" };

aşağıdaki sonuçları elde ederiz:

Burke Burke
Frank Frank
David David

Yukarıdaki sorgu ifadesi bu nokta gösterimi ifadesine genişletiliyor:

var query = names.Where(s1 => s1.Length == 5)
                 .SelectMany(s1 => names, (s1,s2) => new {s1,s2})
                 .Where($1 => $1.s1 == $1.s2) 
                 .Select($1 => $1.s1 + " " + $1.s2);

NotSelectMany'nin bu sürümü, dış ve iç dizilerdeki öğelere göre sonucu üretmek için kullanılan fazladan bir lambda ifadesi alır. Bu lambda ifadesinde iki aralık değişkeni anonim bir türde toplanır. Derleyici, sonraki lambda ifadelerinde bu anonim türü belirtmek için $1 değişken adını icat eder.

Özel bir oluşturucu türü, verilen anahtarlara göre önceki yan tümcelerin öğeleriyle eşleşen başka bir kaynağın öğelerini tanıtan join yan tümcesidir. Join yan tümcesi eşleşen öğeleri tek tek verebilir, ancak bir into yan tümcesiyle belirtilirse, eşleşen öğeler bir grup olarak verilir:

var query = from n in names
            join p in people on n equals p.Name into matching
            select new { Name = n, Count = matching.Count() };

Şaşırtıcı olmayan bir şekilde, bu sorgu daha önce gördüğümüz sorguya doğru genişler:

var query = names.GroupJoin(people, n => n, p => p.Name,                   
           (n, matching) => 
                      new { Name = n, Count = matching.Count() }
);

Genellikle bir sorgunun sonuçlarını sonraki sorguda oluşturucu olarak işlemek yararlı olur. Bunu desteklemek için sorgu ifadeleri, select veya group yan tümcesinin ardından yeni bir sorgu ifadesi eklemek için into anahtar sözcüğünü kullanır. Buna sorgu devamlılığı denir.

into anahtar sözcüğü, bir group by yan tümcesinin sonuçlarının işlenmesinde özellikle yararlıdır. Örneğin, şu programı göz önünde bulundurun:

var query = from item in names
            orderby item
            group item by item.Length into lengthGroups
            orderby lengthGroups.Key descending
            select lengthGroups;

foreach (var group in query) { 
    Console.WriteLine("Strings of length {0}", group.Key);

    foreach (var val in group)
        Console.WriteLine("  {0}", val);
}

Bu program aşağıdaki çıkışları oluşturur:

Strings of length 7
  Everett
Strings of length 6
  Albert
  Connor
  George
  Harris
Strings of length 5
  Burke
  David
  Frank

Bu bölümde, C# uygulamasının sorgu ifadelerini nasıl uyguladığı açıklanmıştır. Diğer diller, açık söz dizimi olan ek sorgu işleçlerini desteklemeyi veya sorgu ifadelerinin hiç olmamasını seçebilir.

Sorgu söz diziminin hiçbir şekilde standart sorgu işleçlerine sabit kablolu olmadığını unutmayın. Bu, uygun adlar ve imzalarla temel alınan yöntemleri uygulayarak sorgu desenini gerçekleştiren her şeye uygulanan tamamen söz dizimsel bir özelliktir. Yukarıda açıklanan standart sorgu işleçleri, IEnumerable<T> arabirimini artırmak için uzantı yöntemlerini kullanarak bunu yapar. Geliştiriciler, gerekli yöntemlerin doğrudan uygulanması veya uzantı yöntemleri olarak eklenmesi yoluyla sorgu düzenine uygun olduğundan emin oldukları sürece, istedikleri türde sorgu söz dizimini kullanabilir.

Bu genişletilebilirlik LINQ projesinin kendisinde, SQL tabanlı veri erişimi için LINQ desenini uygulayan ve XML verileri üzerinde LINQ sorgularına izin veren LINQ to XML LINQ to SQL linq özellikli iki API'nin sağlanmasıyla yararlanılır. Bunların her ikisi de aşağıdaki bölümlerde açıklanmıştır.

LINQ to SQL: SQL Tümleştirmesi

.NET Language-Integrated Sorgusu, yerel programlama dilinin söz dizimi veya derleme zamanı ortamından çıkmadan ilişkisel veri depolarını sorgulamak için kullanılabilir. kod adlı LINQ to SQL olan bu tesis, SQL şema bilgilerinin CLR meta verileriyle tümleştirilmesinden yararlanır. Bu tümleştirme, SQL tablosunu ve görünüm tanımlarını herhangi bir dilden erişilebilen CLR türlerine derler.

LINQ to SQL, hangi CLR türlerinin ve özelliklerinin dış SQL verilerine karşılık geldiğini gösteren [Table] ve [Column] adlı iki temel özniteliği tanımlar. [Table] özniteliği bir sınıfa uygulanabilir ve CLR türünü adlandırılmış bir SQL tablosu veya görünümüyle ilişkilendirir. [Column] özniteliği herhangi bir alana veya özelliğe uygulanabilir ve üyeyi adlandırılmış bir SQL sütunuyla ilişkilendirir. Her iki öznitelik de SQL'e özgü meta verilerin korunmasına izin verecek şekilde parametreleştirilir. Örneğin, şu basit SQL şeması tanımını göz önünde bulundurun:

create table People (
    Name nvarchar(32) primary key not null, 
    Age int not null, 
    CanCode bit not null
)

create table Orders (
    OrderID nvarchar(32) primary key not null, 
    Customer nvarchar(32) not null, 
    Amount int
)

CLR eşdeğeri şöyle görünür:

[Table(Name="People")]
public class Person {
  [Column(DbType="nvarchar(32) not null", Id=true)]
  public string Name; 

  [Column]
  public int Age;

  [Column]
  public bool CanCode;
}

[Table(Name="Orders")]
public class Order {
  [Column(DbType="nvarchar(32) not null", Id=true)]
  public string OrderID; 

  [Column(DbType="nvarchar(32) not null")]        
  public string Customer; 

  [Column]
  public int? Amount; 
}

Not Bu örnek, null atanabilir sütunların CLR'deki null atanabilir türlerle eşlendiği (null atanabilir türler ilk olarak .NET Framework 2.0 sürümünde gösterildi) ve CLR türüne (örneğin, nvarchar, char, text) sahip 1:1 yazışması olmayan SQL türleri için özgün SQL türü CLR meta verilerinde korunur.

bir ilişkisel depoya yönelik bir sorgu vermek için, LINQ deseninin LINQ to SQL uygulaması sorguyu ifade ağacı formundan bir SQL ifadesine çevirir ve uzaktan değerlendirmeye uygun DbCommand nesnesini ADO.NET. Örneğin, şu basit sorguyu göz önünde bulundurun:

// establish a query context over ADO.NET sql connection
DataContext context = new DataContext(
     "Initial Catalog=petdb;Integrated Security=sspi");

// grab variables that represent the remote tables that 
// correspond to the Person and Order CLR types
Table<Person> custs = context.GetTable<Person>();
Table<Order> orders   = context.GetTable<Order>();

// build the query
var query = from c in custs
            from o in orders
            where o.Customer == c.Name
            select new { 
                       c.Name, 
                       o.OrderID,
                       o.Amount,
                       c.Age
            }; 

// execute the query
foreach (var item in query) 
    Console.WriteLine("{0} {1} {2} {3}", 
                      item.Name, item.OrderID, 
                      item.Amount, item.Age);

DataContext türü, standart sorgu işleçlerini SQL'e çeviren basit bir çeviri aracı sağlar. DataContext , depoya erişmek için mevcut ADO.NET IDbConnection kullanır ve yerleşik bir ADO.NET bağlantı nesnesi veya oluşturmak için kullanılabilecek bir bağlantı dizesiyle başlatılabilir.

GetTable yöntemi, uzak tabloyu veya görünümü temsil etmek için sorgu ifadelerinde kullanılabilecek IEnumerable uyumlu değişkenler sağlar. GetTable çağrıları veritabanıyla herhangi bir etkileşime neden olmaz; bunun yerine, uzak tabloyla etkileşim kurma veya sorgu ifadelerini kullanarak görüntüleme potansiyelini temsil eder. Yukarıdaki örneğimizde, program sorgu ifadesi üzerinde yinelenene kadar sorgu depoya aktarılmaz; bu durumda C# dilinde foreach deyimi kullanılır. Program sorgu üzerinde ilk kez yinelendiğinde , DataContext makineleri ifade ağacını depoya gönderilen aşağıdaki SQL deyimine çevirir:

SELECT [t0].[Age], [t1].[Amount], 
       [t0].[Name], [t1].[OrderID]
FROM [Customers] AS [t0], [Orders] AS [t1]
WHERE [t1].[Customer] = [t0].[Name]

Doğrudan yerel programlama diline sorgu özelliği oluşturarak geliştiricilerin ilişkileri CLR türüne statik olarak pişirmek zorunda kalmadan ilişkisel modelin tüm gücünü elde ettiğini unutmayın. Belirtilen kapsamlı nesne/ilişkisel eşleme, bu işlevi isteyen kullanıcılar için bu çekirdek sorgu özelliğinden de yararlanabilir. LINQ to SQL, geliştiricinin nesneler arasındaki ilişkileri tanımlayıp gezinebileceği nesne ilişkisel eşleme işlevi sağlar. Eşleme kullanarak Orders'aCustomer sınıfının bir özelliği olarak başvurabilirsiniz, böylece ikisini birbirine bağlamak için açık birleşimlere ihtiyacınız olmaz. Dış eşleme dosyaları, daha zengin eşleme özellikleri için eşlemenin nesne modelinden ayrılmasını sağlar.

LINQ to XML: XML Tümleştirmesi

.NET Language-Integrated XML sorgusu (LINQ to XML), standart sorgu işleçlerinin yanı sıra alt öğeler, üst öğeler ve eşdüzey öğeler arasında XPath benzeri gezinti sağlayan ağaç özgü işleçler kullanılarak XML verilerinin sorgulanmasına olanak tanır. Xml için mevcut System.Xml okuyucu/yazıcı altyapısıyla tümleşen ve W3C DOM'dan daha kolay kullanılan verimli bir bellek içi gösterim sağlar. XML'yi sorgularla tümleştirme işinin büyük bölümünü oluşturan üç tür vardır: XName, XElement ve XAttribute.

XName , hem öğe hem de öznitelik adları olarak kullanılan ad alanı nitelenmiş tanımlayıcılarla (QNames) başa çıkmak için kullanımı kolay bir yol sağlar. XName , tanımlayıcıların verimli atomizasyonunu saydam bir şekilde işler ve bir QName gerektiğinde simgelerin veya düz dizelerin kullanılmasına izin verir.

XML öğeleri ve öznitelikleri sırasıyla XElement ve XAttribute kullanılarak temsil edilir. XElement ve XAttribute , geliştiricilerin doğal bir söz dizimi kullanarak XML ifadeleri yazmasına olanak tanıyarak normal yapı söz dizimlerini destekler:

var e = new XElement("Person", 
                     new XAttribute("CanCode", true),
                     new XElement("Name", "Loren David"),
                     new XElement("Age", 31));

var s = e.ToString();

Bu, aşağıdaki XML'ye karşılık gelir:

<Person CanCode="true">
  <Name>Loren David</Name> 
  <Age>31</Age> 
</Person>

XML ifadesini oluşturmak için DOM tabanlı fabrika deseninin gerekli olmadığına ve ToString uygulamasının metin xml'ini oluşturduğuna dikkat edin. XML öğeleri varolan bir XmlReader'dan veya bir dize değişmez değerden de oluşturulabilir:

var e2 = XElement.Load(xmlReader);
var e1 = XElement.Parse(
@"<Person CanCode='true'>
  <Name>Loren David</Name>
  <Age>31</Age>
</Person>");

XElement , mevcut XmlWriter türünü kullanarak XML yayma işlemini de destekler.

XElement , sorgu işleçleriyle birlikte çalışarak geliştiricilerin XML olmayan bilgilere sorgu yazmasına ve seçme yan tümcesinin gövdesinde XElement'ler oluşturarak XML sonuçları oluşturmasına olanak sağlar:

var query = from p in people 
            where p.CanCode
            select new XElement("Person", 
                                  new XAttribute("Age", p.Age),
                                  p.Name);

Bu sorgu bir dizi XElement döndürür. XElement'lerin bu tür bir sorgunun sonucundan derlenmesini sağlamak için, XElement oluşturucu doğrudan bağımsız değişken olarak geçirilecek öğe dizilerine izin verir:

var x = new XElement("People",
                  from p in people 
                  where p.CanCode
                  select 
                    new XElement("Person", 
                                   new XAttribute("Age", p.Age),
                                   p.Name));

Bu XML ifadesi aşağıdaki XML sonucunu döndürür:

<People>
  <Person Age="11">Allen Frances</Person> 
  <Person Age="59">Connor Morgan</Person> 
</People>

Yukarıdaki deyimin Visual Basic'e doğrudan çevirisi vardır. Ancak Visual Basic 9.0, doğrudan Visual Basic'ten gelen bildirim temelli XML söz dizimi kullanılarak sorgu ifadelerinin ifade edilmesine olanak tanıyan XML değişmez değerlerinin kullanımını da destekler. Önceki örnek Visual Basic deyimiyle oluşturulabilir:

 Dim x = _
        <People>
             <%= From p In people __
                 Where p.CanCode _

                 Select <Person Age=<%= p.Age %>>p.Name</Person> _
             %>
        </People>

Şimdiye kadarki örneklerde, dille tümleşik sorgu kullanılarak yeni XML değerlerinin nasıl oluşturulur olduğu gösterilmiştir. XElement ve XAttribute türleri, XML yapılarından bilgi ayıklamayı da basitleştirir. XElement , sorgu ifadelerinin geleneksel XPath eksenlerine uygulanmasını sağlayan erişimci yöntemleri sağlar. Örneğin, aşağıdaki sorgu yukarıda gösterilen XElement'ten yalnızca adları ayıklar:

IEnumerable<string> justNames =
    from e in x.Descendants("Person")
    select e.Value;

//justNames = ["Allen Frances", "Connor Morgan"]

XML'den yapılandırılmış değerleri ayıklamak için select yan tümcemizde bir nesne başlatıcı ifadesi kullanırız:

IEnumerable<Person> persons =
    from e in x.Descendants("Person")
    select new Person { 
        Name = e.Value,
        Age = (int)e.Attribute("Age") 
    };

Hem XAttribute hem de XElement'in metin değerini ilkel bir tür olarak ayıklamak için açık dönüştürmeleri desteklediğini unutmayın. Eksik verilerle başa çıkmak için null atanabilir bir türe dönüştürebiliriz:

IEnumerable<Person> persons =
    from e in x.Descendants("Person")
    select new Person { 
        Name = e.Value,
        Age = (int?)e.Attribute("Age") ?? 21
    };

Bu durumda Age özniteliği eksik olduğunda 21 varsayılan değerini kullanırız.

Visual Basic 9.0, XElement'inElements, Attribute ve Descendants erişimci yöntemleri için doğrudan dil desteği sağlayarak XML tabanlı verilere XML ekseni özellikleri adı verilen daha küçük ve doğrudan bir söz dizimi kullanılarak erişilmesine olanak tanır. Yukarıdaki C# deyimini aşağıdaki gibi yazmak için bu işlevi kullanabiliriz:

Dim persons = _
      From e In x...<Person> _   
      Select new Person { _
          .Name = e.Value, _
          .Age = IIF(e.@Age, 21) _
      } 

Visual Basic'te x...<Kişi>, x Descendants koleksiyonundaki tüm öğeleri Person adıyla alırken, e.@Age ifadesi Age. adlı tüm XAttributes'i bulur Değer özelliği koleksiyondaki ilk özniteliği alır ve bu öznitelikte Value özelliğini çağırır.

Özet

.NET Language-Integrated Sorgusu, CLR'ye ve onu hedefleyen dillere sorgu özellikleri ekler. Sorgu tesisi, koşul, projeksiyon ve anahtar ayıklama ifadelerinin opak yürütülebilir kod olarak veya aşağı akış işleme veya çeviri için uygun saydam bellek içi veriler olarak kullanılmasına izin vermek için lambda ifadeleri ve ifade ağaçlarını temel alır. LINQ projesi tarafından tanımlanan standart sorgu işleçleri IEnumerable<T> tabanlı herhangi bir bilgi kaynağı üzerinde çalışır ve ilişkisel ve XML verilerinin dille tümleşik sorgunun avantajlarından yararlanmasına olanak sağlamak için ADO.NET (LINQ to SQL) ve System.Xml (LINQ to XML) ile tümleştirilir.

Özetle Standart Sorgu İşleçleri

İşleç Açıklama
Konum Koşul işlevini temel alan kısıtlama işleci
Select/SelectMany Seçici işlevine dayalı projeksiyon işleçleri
Take/Skip/ TakeWhile/SkipWhile Konum veya koşul işlevine göre bölümleme işleçleri
Join/GroupJoin Anahtar seçici işlevlerine göre işleçleri birleştirme
Concat Birleştirme işleci
OrderBy/ThenBy/OrderByDescending/ThenByDescending İsteğe bağlı anahtar seçici ve karşılaştırıcı işlevlerine göre artan veya azalan düzende sıralayan sıralama işleçleri
Reverse Sıralama işleci bir sıranın sırasını tersine çevirme
GroupBy İsteğe bağlı anahtar seçici ve karşılaştırıcı işlevlerine göre gruplandırma işleci
Distinct Yinelenenleri kaldıran işleci ayarlama
Birleşim/Kesiştir Küme birleşimini veya kesişimini döndüren küme işleçleri
Dışlama Küme işleci küme farkı döndürerek
Asenumerable IEnumerable<T'ye> dönüştürme işleci
ToArray/ToList Dizi veya Liste<T'ye> dönüştürme işleci
ToDictionary/ToLookup Anahtar seçici işlevine göre Sözlük<K,T> veya Arama<K,T'ye> (çoklu sözlük) dönüştürme işleçleri
OfType/Cast Tür bağımsız değişkenine göre filtreleme veya dönüştürme temelinde IEnumerable<T'ye> dönüştürme işleçleri
Sequenceequal Eşitlik işleci çift öğe eşitliğini denetleme
First/FirstOrDefault/Last/LastOrDefault/Single/SingleOrDefault İsteğe bağlı koşul işlevine göre ilk/son/yalnızca öğesini döndüren öğe işleçleri
ElementAt/ElementAtOrDefault Öğe işleçleri konuma göre öğe döndüren
Defaultıfempty Boş diziyi varsayılan değerli tekil sırayla değiştiren öğe işleci
Aralık Bir aralıktaki sayıları döndüren oluşturma işleci
Repeat Belirli bir değerin birden çok oluşumunu döndüren oluşturma işleci
Boş Boş bir dizi döndüren oluşturma işleci
Tümü/Tümü Niceleyici, koşul işlevinin varoluşsal veya evrensel memnuniyetini denetleme
Contains Belirli bir öğenin varlığı için niceleyici denetimi
Count/LongCount İsteğe bağlı koşul işlevine göre öğeleri sayan toplama işleçleri
Toplam/En Az/En Fazla/Ortalama İsteğe bağlı seçici işlevlerine göre işleçleri toplama
Toplama Toplama işlevine ve isteğe bağlı tohuma göre birden çok değeri biriktiren toplama işleci