C#: استعلامات LINQ

1. مفاهيم LINQ ودوافعها

LINQ (Language Integrated Query) هي لغة استعلام مدمجة في لغة البرمجة C# تتيح لك الاستعلام عن مصادر البيانات المختلفة (المجموعات، وقواعد البيانات، وملفات XML، وما إلى ذلك) باستخدام صيغة موحدة. قبل ظهور LINQ، كان الاستعلام عن مصادر البيانات المختلفة يتطلب تعلم واجهات برمجة تطبيقات (APIs) مختلفة — وقد حلت LINQ هذه المشكلة.

▶ مثال

CSHARP
using System;
using System.Linq;

int[] numbers = { 5, 12, 3, 21, 8, 15 };

var result = from n in numbers
             where n > 10
             select n;

foreach (var n in result)
{
    Console.WriteLine(n);
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
12
21
15

2. بناء جملة الاستعلام مقابل بناء جملة الأسلوب

توفر LINQ أسلوبين: صيغة الاستعلام وصيغة الأسلوب. وكلاهما متكافئان من الناحية الوظيفية. وتُستخدم صيغة الأسلوب بشكل أكثر شيوعًا في الممارسة العملية لأنها تدعم عددًا أكبر من العوامل وتسمح بإجراء استدعاءات متسلسلة موجزة.

▶ مثال

CSHARP
using System;
using System.Linq;

int[] numbers = { 5, 12, 3, 21, 8, 15 };

var querySyntax = from n in numbers
                  where n > 10
                  orderby n
                  select n;

var methodSyntax = numbers.Where(n => n > 10).OrderBy(n => n);

Console.WriteLine("Query syntax: " + string.Join(", ", querySyntax));
Console.WriteLine("Method syntax: " + string.Join(", ", methodSyntax));
▶ جرّب الكود
TEXT 📖 للعرض فقط
Query syntax: 12, 15, 21
Method syntax: 12, 15, 21

3. من / حيث / اختر / ترتيب حسب / تجميع

جمل بناء الجملة الأساسية للاستعلام: from تحدد مصدر البيانات ومتغير النطاق، وwhere تقوم بالتصفية، وselect تعرض النتائج، وorderby تقوم بالفرز، وgroup ... by تقوم بالتجميع.

▶ مثال

CSHARP
using System;
using System.Linq;

string[] names = { "Alice", "Bob", "Anna", "Charlie", "Amy" };

var result = from name in names
             where name.StartsWith("A")
             orderby name.Length
             select name;

foreach (var name in result)
{
    Console.WriteLine(name);
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
Amy
Anna
Alice

(1) تجميع حسب المثال

CSHARP
using System;
using System.Linq;

var students = new[]
{
    new { Name = "Zhang", Grade = "A" },
    new { Name = "Li", Grade = "B" },
    new { Name = "Wang", Grade = "A" },
    new { Name = "Zhao", Grade = "C" },
    new { Name = "Sun", Grade = "B" }
};

var groups = from s in students
             group s by s.Grade into g
             select new { Grade = g.Key, Count = g.Count() };

foreach (var g in groups)
{
    Console.WriteLine($"Grade {g.Grade}: {g.Count} student(s)");
}
TEXT 📖 للعرض فقط
Grade A: 2 student(s)
Grade B: 2 student(s)
Grade C: 1 student(s)

4. العوامل الحسابية الشائعة

مشغلات LINQ الشائعة الاستخدام في صيغة الأسلوب:

المشغل الغرض
المكان عناصر التصفية
تحديد مشروع/تحويل
OrderBy / OrderByDescending الترتيب التصاعدي/التنازلي
ThenBy الفرز الثانوي
GroupBy عناصر المجموعة
First / FirstOrDefault الحصول على العنصر الأول
Single / SingleOrDefault استرداد العنصر الوحيد
العدد عناصر العدد
أي التحقق من وجود أي عنصر
الكل التحقق مما إذا كانت جميع العناصر تستوفي الشرط
المجموع / المتوسط المجموع / المتوسط
الحد الأدنى / الحد الأقصى الحد الأدنى / الحد الأقصى
تخطي / اختيار تخطي / اختيار العدد المحدد
مميز إزالة التكرارات
ToList / ToArray / ToDictionary تحويل إلى مجموعة

▶ مثال

CSHARP
using System;
using System.Linq;

int[] numbers = { 3, 7, 2, 9, 5, 7, 3, 8 };

Console.WriteLine("Where > 5: " + string.Join(", ", numbers.Where(n => n > 5)));
Console.WriteLine("Select x2: " + string.Join(", ", numbers.Select(n => n * 2)));
Console.WriteLine("OrderBy: " + string.Join(", ", numbers.OrderBy(n => n)));
Console.WriteLine("OrderByDescending: " + string.Join(", ", numbers.OrderByDescending(n => n)));
Console.WriteLine("Distinct: " + string.Join(", ", numbers.Distinct()));
Console.WriteLine("First: " + numbers.First());
Console.WriteLine("FirstOrDefault > 10: " + numbers.FirstOrDefault(n => n > 10));
Console.WriteLine("Count > 5: " + numbers.Count(n => n > 5));
Console.WriteLine("Any > 8: " + numbers.Any(n => n > 8));
Console.WriteLine("All > 1: " + numbers.All(n => n > 1));
Console.WriteLine("Sum: " + numbers.Sum());
Console.WriteLine("Average: " + numbers.Average());
Console.WriteLine("Min: " + numbers.Min());
Console.WriteLine("Max: " + numbers.Max());
Console.WriteLine("Skip 3 Take 2: " + string.Join(", ", numbers.Skip(3).Take(2)));
▶ جرّب الكود
TEXT 📖 للعرض فقط
Where > 5: 7, 9, 7, 8
Select x2: 6, 14, 4, 18, 10, 14, 6, 16
OrderBy: 2, 3, 3, 5, 7, 7, 8, 9
OrderByDescending: 9, 8, 7, 7, 5, 3, 3, 2
Distinct: 3, 7, 2, 9, 5, 8
First: 3
FirstOrDefault > 10: 0
Count > 5: 4
Any > 8: True
All > 1: True
Sum: 44
Average: 5.5
Min: 2
Max: 9
Skip 3 Take 2: 9, 5

5. استخدام LINQ مع القوائم والمصفوفات

تعمل LINQ على أي كائن ينفذ واجهة IEnumerable<T>، بما في ذلك List<T> والمصفوفات T[].

▶ مثال

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

List```<string>``` fruits = new List```<string>``` { "apple", "banana", "cherry", "date" };

var longNames = fruits.Where(f => f.Length > 5).Select(f => f.ToUpper());
Console.WriteLine("List query: " + string.Join(", ", longNames));

int[] scores = { 88, 72, 95, 60, 85 };
var topScores = scores.Where(s => s >= 85).OrderByDescending(s => s);
Console.WriteLine("Array query: " + string.Join(", ", topScores));
▶ جرّب الكود
TEXT 📖 للعرض فقط
List query: BANANA, CHERRY
Array query: 95, 88, 85

6. LINQ مع Dictionary

عند استخدام LINQ على قاموس، يكون كل عنصر عبارة عن KeyValuePair<TKey, TValue>، ويتم الوصول إليه عبر .Key و.Value.

▶ مثال

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

var prices = new Dictionary`<string, decimal>`
{
    { "Apple", 5.5m },
    { "Banana", 3.2m },
    { "Orange", 7.8m },
    { "Grape", 12.0m }
};

var expensive = prices.Where(kv => kv.Value > 5m)
                      .OrderByDescending(kv => kv.Value)
                      .Select(kv => $"{kv.Key}: {kv.Value}");

foreach (var item in expensive)
{
    Console.WriteLine(item);
}

var priceDict = prices.ToDictionary(kv => kv.Key, kv => kv.Value * 2);
Console.WriteLine("Doubled orange price: " + priceDict["Orange"]);
▶ جرّب الكود
TEXT 📖 للعرض فقط
Grape: 12.0
Orange: 7.8
Apple: 5.5
Doubled orange price: 15.6

7. التنفيذ المؤجل مقابل التنفيذ الفوري

يتم تأجيل استعلامات LINQ افتراضيًّا: فالتعريف الاستعلام لا يؤدي إلى تنفيذه — ولا يتم تنفيذ الاستعلام إلا عند إجراء تكرار على النتائج (على سبيل المثال، باستخدام foreach). أما استدعاء طرق مثل ToList() أو ToArray() أو Count() أو First() فيؤدي إلى التنفيذ الفوري.

▶ مثال

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

var numbers = new List```<int>``` { 1, 2, 3 };

var query = numbers.Where(n => n > 1);

numbers.Add(4);
numbers.Add(5);

Console.WriteLine("Deferred execution includes elements added later:");
foreach (var n in query)
{
    Console.WriteLine(n);
}

var immediate = numbers.Where(n => n > 1).ToList();

numbers.Add(6);

Console.WriteLine("Immediate execution does not include 6 added later:");
foreach (var n in immediate)
{
    Console.WriteLine(n);
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
Deferred execution includes elements added later:
2
3
4
5
Immediate execution does not include 6 added later:
2
3
4
5

💡 يعني «التنفيذ المؤجل» أن إعادة التمرير على نتائج الاستعلام تؤدي إلى إعادة حسابها في كل مرة. إذا كنت بحاجة إلى استخدام النتائج عدة مرات، فاستدعِ ToList() لتخزينها مؤقتًا.


8. IQueryable مقابل IEnumerable

يُستخدم IEnumerable<T> في استعلامات المجموعات الموجودة في الذاكرة (LINQ to Objects)، بينما يُستخدم IQueryable<T> في استعلامات مصادر البيانات البعيدة (مثل قواعد البيانات). ويمكنه ترجمة أشجار التعبيرات إلى لغات استعلام مثل SQL وتنفيذها من جانب الخادم.

▶ مثال

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

List```<int>``` data = new List```<int>``` { 10, 20, 30, 40, 50 };

IEnumerable```<int>``` enumerableQuery = data.Where(n => n > 25);
IQueryable```<int>``` queryableQuery = data.AsQueryable().Where(n => n > 25);

Console.WriteLine("IEnumerable result: " + string.Join(", ", enumerableQuery));
Console.WriteLine("IQueryable result: " + string.Join(", ", queryableQuery));
Console.WriteLine("IQueryable expression: " + queryableQuery.Expression);
▶ جرّب الكود
TEXT 📖 للعرض فقط
IEnumerable result: 30, 40, 50
IQueryable result: 30, 40, 50
IQueryable expression: System.Collections.Generic.List`1[System.Int32].Where(n => (n > 25))

⚠️ عند استخدام أدوات ORM مثل EF Core، يقوم IQueryable بتنفيذ عملية التصفية على مستوى قاعدة البيانات، بينما يقوم IEnumerable بتحميل جميع البيانات إلى الذاكرة أولاً ثم يقوم بالتصفية — وقد يكون الفرق في الأداء كبيرًا.


❓ أسئلة شائعة

س أيهما أفضل — صيغة الاستعلام أم صيغة الأسلوب؟
ج تُستخدم صيغة الأسلوب بشكل أكثر شيوعًا، وتدعم جميع العوامل الحسابية، وتتيح إجراء استدعاءات متسلسلة موجزة. أما صيغة الاستعلام، فهي لا تدعم سوى بعض العوامل الحسابية، لكنها قد تكون أسهل في القراءة عند إجراء عمليات ربط/تجميع معقدة.
س ما الفرق بين FirstOrDefault و First؟
ج تُحدث First استثناءً في حالة عدم العثور على أي عنصر؛ بينما تُرجع FirstOrDefault القيمة الافتراضية (null للأنواع المرجعية، و0 للأنواع القيمية). يُفضل استخدام FirstOrDefault في معظم الحالات.
س ما الفرق بين "Single" و"First"؟
ج تتطلب دالة "Single" أن يحتوي التسلسل على عنصر مطابق واحد فقط — وتُحدث استثناءً إذا كان هناك أكثر من عنصر واحد. أما دالة "First" فتُرجع ببساطة أول عنصر مطابق.
س لماذا يؤدي تكرار نتائج LINQ مرتين إلى تنفيذ الاستعلام مرتين؟
ج بسبب التنفيذ المؤجل، يتم إعادة الحساب في كل تكرار. استخدم ToList() لتخزين النتائج مؤقتًا وتجنب الحسابات الزائدة.
س كيف يمكن الاختيار بين IQueryable و IEnumerable؟
ج استخدم IQueryable لاستعلامات قاعدة البيانات بحيث يتم تنفيذ SQL على جانب الخادم؛ واستخدم IEnumerable للمجموعات الموجودة في الذاكرة.

📖 ملخص

📝 تمارين

  1. بالنظر إلى int[] nums = { 34, 7, 21, 56, 12, 89, 3 }، استخدم صيغة الطريقة لإيجاد جميع الأعداد الزوجية الأكبر من 20 مرتبة ترتيبًا تنازليًّا، ثم اعرض النتيجة
  2. حدد قائمة بالطلاب (الاسم، العمر، الفصل)، واستخدم LINQ لتجميعها حسب الفصل، ثم اعرض عدد الطلاب في كل فصل
  3. إنشاء جدول Dictionary<string, int> لتخزين أسماء المنتجات ومخزونها، واستخدام LINQ للبحث عن أسماء المنتجات التي يقل مخزونها عن 10، وتحويلها إلى List<string>
  4. اكتب كودًا يوضح التنفيذ المؤجل: قم بتعريف استعلام، ثم قم بتعديل المجموعة الأصلية، ولاحظ ما إذا كانت نتيجة التكرار تتضمن عناصر جديدة؛ ثم استخدم ToList() لمقارنة ذلك بسلوك التنفيذ الفوري
  5. اشرح في جملة واحدة لماذا يعتبر IQueryable أكثر كفاءة من IEnumerable في EF Core
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%