C#: استعلامات LINQ
1. مفاهيم LINQ ودوافعها
LINQ (Language Integrated Query) هي لغة استعلام مدمجة في لغة البرمجة C# تتيح لك الاستعلام عن مصادر البيانات المختلفة (المجموعات، وقواعد البيانات، وملفات XML، وما إلى ذلك) باستخدام صيغة موحدة. قبل ظهور LINQ، كان الاستعلام عن مصادر البيانات المختلفة يتطلب تعلم واجهات برمجة تطبيقات (APIs) مختلفة — وقد حلت LINQ هذه المشكلة.
▶ مثال
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);
}
12
21
15
2. بناء جملة الاستعلام مقابل بناء جملة الأسلوب
توفر LINQ أسلوبين: صيغة الاستعلام وصيغة الأسلوب. وكلاهما متكافئان من الناحية الوظيفية. وتُستخدم صيغة الأسلوب بشكل أكثر شيوعًا في الممارسة العملية لأنها تدعم عددًا أكبر من العوامل وتسمح بإجراء استدعاءات متسلسلة موجزة.
▶ مثال
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));
Query syntax: 12, 15, 21
Method syntax: 12, 15, 21
3. من / حيث / اختر / ترتيب حسب / تجميع
جمل بناء الجملة الأساسية للاستعلام: from تحدد مصدر البيانات ومتغير النطاق، وwhere تقوم بالتصفية، وselect تعرض النتائج، وorderby تقوم بالفرز، وgroup ... by تقوم بالتجميع.
▶ مثال
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);
}
Amy
Anna
Alice
(1) تجميع حسب المثال
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)");
}
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 | تحويل إلى مجموعة |
▶ مثال
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)));
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[].
▶ مثال
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));
List query: BANANA, CHERRY
Array query: 95, 88, 85
6. LINQ مع Dictionary
عند استخدام LINQ على قاموس، يكون كل عنصر عبارة عن KeyValuePair<TKey, TValue>، ويتم الوصول إليه عبر .Key و.Value.
▶ مثال
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"]);
Grape: 12.0
Orange: 7.8
Apple: 5.5
Doubled orange price: 15.6
7. التنفيذ المؤجل مقابل التنفيذ الفوري
يتم تأجيل استعلامات LINQ افتراضيًّا: فالتعريف الاستعلام لا يؤدي إلى تنفيذه — ولا يتم تنفيذ الاستعلام إلا عند إجراء تكرار على النتائج (على سبيل المثال، باستخدام foreach). أما استدعاء طرق مثل ToList() أو ToArray() أو Count() أو First() فيؤدي إلى التنفيذ الفوري.
▶ مثال
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);
}
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 وتنفيذها من جانب الخادم.
▶ مثال
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);
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 بتحميل جميع البيانات إلى الذاكرة أولاً ثم يقوم بالتصفية — وقد يكون الفرق في الأداء كبيرًا.
❓ أسئلة شائعة
📖 ملخص
- LINQ هي لغة الاستعلام الموحدة المدمجة في لغة C#، وهي تدعم المجموعات وقواعد البيانات وملفات XML ومصادر البيانات الأخرى
- تشبه صيغة الاستعلام لغة SQL؛ بينما تستخدم صيغة الطرق استدعاءات لامدا متسلسلة — ويمكن الجمع بينهما
- البنود الأساسية: from، where، select، order by، group by
- العوامل الشائعة: Where، Select، OrderBy، GroupBy، First، Count، Any، Sum، Skip، Take، إلخ.
- يعمل LINQ مع List وArray وDictionary وأي كائن من فئة IEnumerable
<T> - يعني «التنفيذ المؤجل» أن الاستعلامات تُنفَّذ في وقت التكرار؛ أما ToList/ToArray فتؤديان إلى التنفيذ الفوري
- تُستخدم IQueryable لمصادر البيانات البعيدة (قواعد البيانات)؛ بينما تُستخدم IEnumerable للمجموعات الموجودة في الذاكرة
📝 تمارين
- بالنظر إلى
int[] nums = { 34, 7, 21, 56, 12, 89, 3 }، استخدم صيغة الطريقة لإيجاد جميع الأعداد الزوجية الأكبر من 20 مرتبة ترتيبًا تنازليًّا، ثم اعرض النتيجة - حدد قائمة بالطلاب (الاسم، العمر، الفصل)، واستخدم LINQ لتجميعها حسب الفصل، ثم اعرض عدد الطلاب في كل فصل
- إنشاء جدول
Dictionary<string, int>لتخزين أسماء المنتجات ومخزونها، واستخدام LINQ للبحث عن أسماء المنتجات التي يقل مخزونها عن 10، وتحويلها إلىList<string> - اكتب كودًا يوضح التنفيذ المؤجل: قم بتعريف استعلام، ثم قم بتعديل المجموعة الأصلية، ولاحظ ما إذا كانت نتيجة التكرار تتضمن عناصر جديدة؛ ثم استخدم
ToList()لمقارنة ذلك بسلوك التنفيذ الفوري - اشرح في جملة واحدة لماذا يعتبر
IQueryableأكثر كفاءة منIEnumerableفي EF Core