Verileri sorgulamak için C# LINQ sorguları yazma

Giriş niteliğindeki Dil Tümleşik Sorgu (LINQ) belgelerindeki sorguların çoğu LINQ bildirim temelli sorgu söz dizimi kullanılarak yazılır. Ancak, kod derlendiğinde sorgu söz dizimi .NET ortak dil çalışma zamanı (CLR) için yöntem çağrılarına çevrilmelidir. Bu yöntem çağrıları, , Select, MaxGroupByJoinve Averagegibi Whereadlara sahip standart sorgu işleçlerini çağırır. Sorgu söz dizimi yerine yöntem söz dizimini kullanarak bunları doğrudan çağırabilirsiniz.

Sorgu söz dizimi ve yöntem söz dizimi aynı olsa da, sorgu söz dizimi genellikle daha basit ve daha kolay okunur. Bazı sorgular yöntem çağrıları olarak ifade edilmelidir. Örneğin, belirtilen koşulla eşleşen öğe sayısını alan bir sorguyu ifade etmek için yöntem çağrısı kullanmanız gerekir. Ayrıca, kaynak dizisinde en yüksek değere sahip öğeyi alan bir sorgu için yöntem çağrısı kullanmanız gerekir. Ad alanında standart sorgu işleçleri için başvuru belgelerinde System.Linq genellikle yöntem söz dizimi kullanılır. Sorgularda ve sorgu ifadelerinde yöntem söz dizimlerinin nasıl kullanılacağını biliyor olmalısınız.

Standart sorgu işleci uzantısı yöntemleri

Aşağıdaki örnekte basit bir sorgu ifadesi ve yöntem tabanlı sorgu olarak yazılmış olan eş zamanlı eşdeğer sorgu gösterilmektedir.

int[] numbers = [ 5, 10, 8, 3, 6, 12 ];

//Query syntax:
IEnumerable<int> numQuery1 =
    from num in numbers
    where num % 2 == 0
    orderby num
    select num;

//Method syntax:
IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);

foreach (int i in numQuery1)
{
    Console.Write(i + " ");
}
Console.WriteLine(System.Environment.NewLine);
foreach (int i in numQuery2)
{
    Console.Write(i + " ");
}

İki örnekten alınan çıkış aynıdır. Sorgu değişkeninin türünün her iki biçimde de aynı olduğunu görebilirsiniz: IEnumerable<T>.

Yöntem tabanlı sorguyu anlamak için daha yakından inceleyelim. İfadenin sağ tarafında yan tümcesinin where nesnesi üzerinde numbers bir örnek yöntemi olarak ifade edildiğine ve türüne IEnumerable<int>sahip olduğuna dikkat edin. Genel IEnumerable<T> arabirim hakkında bilgi sahibiyseniz, bir Where yöntemi olmadığını biliyorsunuz. Ancak, Visual Studio IDE'de IntelliSense tamamlama listesini çağırırsanız, yalnızca bir Where yöntemi değil, aynı zamanda , SelectMany, Joinve Orderbygibi Selectdiğer birçok yöntemi görürsünüz. Bu yöntemler standart sorgu işleçlerini uygular.

IntelliSense'teki tüm standart sorgu işleçlerini gösteren ekran görüntüsü.

Daha fazla yöntem içeriyor gibi IEnumerable<T> görünse de, içermez. Standart sorgu işleçleri uzantı yöntemleri olarak uygulanır. Uzantı yöntemleri var olan bir türü "genişletir"; türünde örnek yöntemleriymiş gibi çağrılabilirler. Standart sorgu işleçleri genişletilir IEnumerable<T> ve bu nedenle yazabilirsiniz numbers.Where(...).

Uzantı yöntemlerini kullanmak için bunları yönergelerle using kapsama alırsınız. Uygulamanızın bakış açısından bir uzantı yöntemi ve normal örnek yöntemi aynıdır.

Uzantı yöntemleri hakkında daha fazla bilgi için bkz . Uzantı Yöntemleri. Standart sorgu işleçleri hakkında daha fazla bilgi için bkz . Standart Sorgu İşleçlerine Genel Bakış (C#). Entity Framework ve LINQ to XML gibi bazı LINQ sağlayıcıları, dışındaki IEnumerable<T>diğer türler için kendi standart sorgu işleçlerini ve uzantı yöntemlerini uygular.

Lambda ifadeleri

Önceki örnekte, koşullu ifadenin (num % 2 == 0) yöntemine Enumerable.Where satır içi bağımsız değişken olarak geçirildiğini göreceksiniz: Where(num => num % 2 == 0). Bu satır içi ifade bir lambda ifadesidir. Aksi takdirde daha hantal biçimde yazılması gereken kod yazmanın kullanışlı bir yoludur. num işlecinin sol tarafındaki giriş değişkenidir ve bu değişken sorgu ifadesindekine num karşılık gelir. Derleyici, bunun genel IEnumerable<T> bir tür num olduğunu numbers bildiği için türünü çıkarsayabilir. Lambda gövdesi, sorgu söz dizimindeki veya başka bir C# ifadesi veya deyimindeki ifadeyle aynıdır. Yöntem çağrılarını ve diğer karmaşık mantığı içerebilir. Dönüş değeri yalnızca ifade sonucudur. Bazı sorgular yalnızca yöntem söz diziminde ifade edilebilir ve bunlardan bazıları lambda ifadeleri gerektirir. Lambda ifadeleri, LINQ araç kutunuzda güçlü ve esnek bir araçtır.

Sorguların birlenebilirliği

Önceki kod örneğinde Enumerable.OrderBy yöntemi çağrısındaki nokta işleci kullanılarak çağrılır Where. Where filtrelenmiş bir dizi oluşturur ve ardından Orderby tarafından Whereüretilen diziyi sıralar. Sorgular bir IEnumerabledöndüreceği için, yöntem çağrılarını birbirine zincirleyerek bunları yöntem söz diziminde oluşturursunuz. Sorgu söz dizimini kullanarak sorgu yazdığınızda derleyici bu bileşimi yapar. Sorgu değişkeni sorgunun sonuçlarını depolamadığından, sorguyu yürütürken bile istediğiniz zaman değiştirebilir veya yeni bir sorgunun temeli olarak kullanabilirsiniz.

Aşağıdaki örneklerde, daha önce listelenen her yaklaşımı kullanarak bazı basit LINQ sorguları gösterilmektedir.

Not

Bu sorgular basit bellek içi koleksiyonlar üzerinde çalışır; ancak, temel söz dizimi LINQ to Entities ve LINQ to XML içinde kullanılan söz dizimi ile aynıdır.

Örnek - Sorgu söz dizimi

Sorgu ifadeleri oluşturmak için çoğu sorguyu sorgu söz dizimi ile yazarsınız. Aşağıdaki örnekte üç sorgu ifadesi gösterilmektedir. İlk sorgu ifadesi, yan where tümcesi ile koşullar uygulayarak sonuçları filtrelemeyi veya kısıtlamayı gösterir. Kaynak dizideki değerleri 7'den büyük veya 3'ten küçük olan tüm öğeleri döndürür. İkinci ifade, döndürülen sonuçların nasıl sıralanması gerektiğini gösterir. Üçüncü ifadede sonuçların bir anahtara göre nasıl gruplandığı gösterilir. Bu sorgu, sözcüğün ilk harfine göre iki grup döndürür.

List<int> numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];

// The query variables can also be implicitly typed by using var

// Query #1.
IEnumerable<int> filteringQuery =
    from num in numbers
    where num is < 3 or > 7
    select num;

// Query #2.
IEnumerable<int> orderingQuery =
    from num in numbers
    where num is < 3 or > 7
    orderby num ascending
    select num;

// Query #3.
string[] groupingQuery = ["carrots", "cabbage", "broccoli", "beans", "barley"];
IEnumerable<IGrouping<char, string>> queryFoodGroups =
    from item in groupingQuery
    group item by item[0];

Sorguların türü şeklindedir IEnumerable<T>. Bu sorguların tümü aşağıdaki örnekte gösterildiği gibi kullanılarak var yazılabilir:

var query = from num in numbers...

Önceki her örnekte sorgular, siz bir deyimdeki veya başka bir foreach deyimdeki sorgu değişkeni üzerinde yinelene kadar yürütülmeyecektir.

Örnek - Yöntem söz dizimi

Bazı sorgu işlemleri yöntem çağrısı olarak ifade edilmelidir. Bu tür en yaygın yöntemler, , Max, , AverageMinve gibi Sumtek sayısal değerler döndüren yöntemlerdir. Bu yöntemler her zaman tek bir değer döndüreceği ve ek sorgu işlemi için kaynak işlevi göremeyecekleri için herhangi bir sorguda en son çağrılmalıdır. Aşağıdaki örnekte sorgu ifadesindeki bir yöntem çağrısı gösterilmektedir:

List<int> numbers1 = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];
List<int> numbers2 = [15, 14, 11, 13, 19, 18, 16, 17, 12, 10];

// Query #4.
double average = numbers1.Average();

// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);

Yöntemin System.Action veya System.Func<TResult> parametreleri varsa, bu bağımsız değişkenler aşağıdaki örnekte gösterildiği gibi lambda ifadesi biçiminde sağlanır:

// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);

Önceki sorgularda yalnızca Sorgu #4, genel IEnumerable<T> bir koleksiyon değil tek bir değer döndürdüğünden hemen yürütülür. Yönteminin kendisi değerini hesaplamak için veya benzer kod kullanır foreach .

Önceki sorguların her biri, aşağıdaki örnekte gösterildiği gibi 'var' ile örtük yazma kullanılarak yazılabilir:

// var is used for convenience in these queries
double average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);

Örnek - Karma sorgu ve yöntem söz dizimi

Bu örnekte, sorgu yan tümcesinin sonuçlarında yöntem söz diziminin nasıl kullanılacağı gösterilmektedir. Sorgu ifadesini parantez içine almanız ve ardından nokta işlecini uygulayıp yöntemini çağırması gerekir. Aşağıdaki örnekte, #7 sorgusu değeri 3 ile 7 arasında olan sayıların sayısını döndürür. Ancak genel olarak yöntem çağrısının sonucunu depolamak için ikinci bir değişken kullanmak daha iyidir. Bu şekilde sorgunun sonuçlarıyla karıştırılma olasılığı daha düşüktür.

// Query #7.

// Using a query expression with method syntax
var numCount1 = (
    from num in numbers1
    where num is > 3 and < 7
    select num
).Count();

// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
    from num in numbers1
    where num is > 3 and < 7
    select num;

var numCount2 = numbersQuery.Count();

Sorgu #7 bir koleksiyon değil tek bir değer döndürdüğünden sorgu hemen yürütülür.

Önceki sorgu aşağıdaki gibi ile varörtük yazma kullanılarak yazılabilir:

var numCount = (from num in numbers...

Yöntem söz diziminde aşağıdaki gibi yazılabilir:

var numCount = numbers.Count(n => n is > 3 and < 7);

Aşağıdaki gibi açık yazma kullanılarak yazılabilir:

int numCount = numbers.Count(n => n is > 3 and < 7);

Çalışma zamanında koşul filtrelerini dinamik olarak belirtme

Bazı durumlarda, çalışma zamanına kadar yan tümcedeki where kaynak öğelere uygulamanız gereken koşul sayısını bilmezsiniz. Birden çok koşul filtresini dinamik olarak belirtmenin Contains bir yolu, aşağıdaki örnekte gösterildiği gibi yöntemini kullanmaktır. Sorgu, sorgunun yürütüldüğü zaman değerine id göre farklı sonuçlar döndürür.

int[] ids = [111, 114, 112];

var queryNames =
    from student in students
    where ids.Contains(student.ID)
    select new
    {
        student.LastName,
        student.ID
    };

foreach (var name in queryNames)
{
    Console.WriteLine($"{name.LastName}: {name.ID}");
}

/* Output:
    Garcia: 114
    O'Donnell: 112
    Omelchenko: 111
 */

// Change the ids.
ids = [122, 117, 120, 115];

// The query will now return different results
foreach (var name in queryNames)
{
    Console.WriteLine($"{name.LastName}: {name.ID}");
}

/* Output:
    Adams: 120
    Feng: 117
    Garcia: 115
    Tucker: 122
 */

Önceden belirlenmiş alternatif sorgular arasından seçim yapmak için veya switchgibi if... else denetim akışı deyimlerini kullanabilirsiniz. Aşağıdaki örnekte, studentQuery çalışma zamanı değeri oddYear veya falseise true farklı where bir yan tümcesi kullanır.

void FilterByYearType(bool oddYear)
{
    IEnumerable<Student> studentQuery = oddYear
        ? (from student in students
           where student.Year is GradeLevel.FirstYear or GradeLevel.ThirdYear
           select student)
        : (from student in students
           where student.Year is GradeLevel.SecondYear or GradeLevel.FourthYear
           select student);
    var descr = oddYear ? "odd" : "even";
    Console.WriteLine($"The following students are at an {descr} year level:");
    foreach (Student name in studentQuery)
    {
        Console.WriteLine($"{name.LastName}: {name.ID}");
    }
}

FilterByYearType(true);

/* Output:
    The following students are at an odd year level:
    Fakhouri: 116
    Feng: 117
    Garcia: 115
    Mortensen: 113
    Tucker: 119
    Tucker: 122
 */

FilterByYearType(false);

/* Output:
    The following students are at an even year level:
    Adams: 120
    Garcia: 114
    Garcia: 118
    O'Donnell: 112
    Omelchenko: 111
    Zabokritski: 121
 */

Sorgu ifadelerinde boş değerler işleme

Bu örnek, kaynak koleksiyonlardaki olası null değerlerin nasıl işleneceğini gösterir. gibi bir IEnumerable<T> nesne koleksiyonu, değeri null olan öğeler içerebilir. Kaynak koleksiyon ise null veya değeri nullolan bir öğe içeriyorsa ve sorgunuz değerleri işlemezse null , sorguyu yürüttüğünüzde bir NullReferenceException oluşturulur.

Aşağıdaki örnekte gösterildiği gibi null başvuru özel durumunu önlemek için savunmayla kodlayabilirsiniz:

var query1 =
    from c in categories
    where c != null
    join p in products on c.ID equals p?.CategoryID
    select new
    {
        Category = c.Name,
        Name = p.Name
    };

Önceki örnekte yan tümcesi, where kategoriler dizisindeki tüm null öğeleri filtreler. Bu teknik, join yan tümcesindeki null denetiminden bağımsızdır. Bu örnekte null olan koşullu ifade, için Nullable<int>kısaltma olan türünde int?olduğundan çalışırProducts.CategoryID.

Birleştirme yan tümcesinde, karşılaştırma anahtarlarından yalnızca biri null değer türündeyse, diğerini sorgu ifadesinde null atanabilir bir değer türüne dönüştürebilirsiniz. Aşağıdaki örnekte, türündeki int?değerleri içeren bir sütun olduğunu EmployeeID varsayalım:

var query =
    from o in db.Orders
    join e in db.Employees
        on o.EmployeeID equals (int?)e.EmployeeID
    select new { o.OrderID, e.FirstName };

Örneklerin equals her birinde sorgu anahtar sözcüğü kullanılır. ve is not nulliçin desenler içeren desen is null eşleştirmeyi de kullanabilirsiniz. Sorgu sağlayıcıları yeni C# söz dizimini doğru yorumlayamadığından bu desenler LINQ sorgularında önerilmez. Sorgu sağlayıcısı, C# sorgu ifadelerini Entity Framework Core gibi yerel bir veri biçimine çeviren bir kitaplıktır. Sorgu sağlayıcıları arabirimi uygulayan System.Linq.IQueryProvider veri kaynakları oluşturmak için arabirimi uygular System.Linq.IQueryable<T> .

Sorgu ifadelerinde özel durumları işleme

Sorgu ifadesi bağlamında herhangi bir yöntemi çağırmak mümkündür. Veri kaynağının içeriğini değiştirme veya özel durum oluşturma gibi yan etki oluşturabilen bir sorgu ifadesinde hiçbir yöntemi çağırmayın. Bu örnekte, özel durum işlemeyle ilgili genel .NET yönergelerini ihlal etmeden sorgu ifadesindeki yöntemleri çağırdığınızda özel durumların nasıl yükseltilmesinin önlenmesi gösterilmektedir. Bu yönergeler, belirli bir bağlamda neden atıldığını anladığınızda belirli bir özel durumu yakalamanın kabul edilebilir olduğunu belirtir. Daha fazla bilgi için bkz . Özel Durumlar için En İyi Yöntemler.

Son örnek, bir sorgunun yürütülmesi sırasında özel durum oluşturmanız gerektiğinde bu durumların nasıl işleneceğini gösterir.

Aşağıdaki örnekte, özel durum işleme kodunun sorgu ifadesinin dışına nasıl taşınacakları gösterilmektedir. Bu yeniden düzenleme yalnızca yöntem sorgunun yerel değişkenlerine bağımlı olmadığında mümkündür. Sorgu ifadesinin dışındaki özel durumlarla başa çıkmak daha kolaydır.

// A data source that is very likely to throw an exception!
IEnumerable<int> GetData() => throw new InvalidOperationException();

// DO THIS with a datasource that might
// throw an exception.
IEnumerable<int>? dataSource = null;
try
{
    dataSource = GetData();
}
catch (InvalidOperationException)
{
    Console.WriteLine("Invalid operation");
}

if (dataSource is not null)
{
    // If we get here, it is safe to proceed.
    var query =
        from i in dataSource
        select i * i;

    foreach (var i in query)
    {
        Console.WriteLine(i.ToString());
    }
}

catch (InvalidOperationException) Yukarıdaki örnekteki blokta, özel durumu uygulamanız için uygun şekilde işleyin (veya işlemeyin).

Bazı durumlarda, sorgunun içinden oluşan bir özel duruma en iyi yanıt, sorgu yürütmeyi hemen durdurmak olabilir. Aşağıdaki örnek, sorgu gövdesinden oluşturulabilecek özel durumların nasıl işleneceğini gösterir. Bunun SomeMethodThatMightThrow , sorgu yürütmenin durdurulmasını gerektiren bir özel duruma neden olabileceğini varsayalım.

try Blok, sorgunun foreach kendisini değil döngünün içine alır. foreach Döngü, sorgunun yürütüldiği noktadır. Sorgu yürütülürken çalışma zamanı özel durumları oluşturulur. Bu nedenle döngüde foreach işlenmelidir.

// Not very useful as a general purpose method.
string SomeMethodThatMightThrow(string s) =>
    s[4] == 'C' ?
        throw new InvalidOperationException() :
        @"C:\newFolder\" + s;

// Data source.
string[] files = ["fileA.txt", "fileB.txt", "fileC.txt"];

// Demonstration query that throws.
var exceptionDemoQuery =
    from file in files
    let n = SomeMethodThatMightThrow(file)
    select n;

try
{
    foreach (var item in exceptionDemoQuery)
    {
        Console.WriteLine($"Processing {item}");
    }
}
catch (InvalidOperationException e)
{
    Console.WriteLine(e.Message);
}

/* Output:
    Processing C:\newFolder\fileA.txt
    Processing C:\newFolder\fileB.txt
    Operation is not valid due to the current state of the object.
 */

Bir blokta finally yükseltmeyi beklediğiniz özel durumu yakalamayı ve/veya gerekli temizlemeyi yapmayı unutmayın.

Ayrıca bkz.