الاستعلامات المجمّعة والأداء
حسّنوا أداء الاستعلامات باستخدام EF.CompileQuery وAsNoTracking والإسقاط مع Select.
الاستعلامات المجمّعة والأداء درس مجاني في C# Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C# Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C# Academy 4 دروس في المجموع.
ترجمة استعلامات EF Core
في كل مرة ينفّذ فيها EF Core استعلام LINQ، يجب عليه تحليل شجرة التعبير وترجمتها إلى SQL والتحقق منها. ويتراكم هذا العبء في المسارات الساخنة. يوفر EF Core عدة أدوات لتقليله إلى أدنى حد.
AsNoTracking للاستعلامات التي تقتصر على القراءة
تتجاوز AsNoTracking() تسجيل الكيانات في متعقّب التغييرات. وبالنسبة إلى الاستعلامات التي تقتصر على القراءة، مثل استجابات API والتقارير، فهذا أكبر تحسين سهل عادةً — إذ يجعل التنفيذ أسرع بمقدار 2–5 مرات مع استهلاك ذاكرة أقل.
// With tracking (default) — for entities you'll modify
var tracked = await _db.Products.FirstOrDefaultAsync(p => p.Id == 1);
// Without tracking — for read-only
var dto = await _db.Products
.AsNoTracking()
.Where(p => p.IsActive)
.Select(p => new ProductDto { Id = p.Id, Name = p.Name })
.ToListAsync();الاستعلامات المترجمة
تترجم EF.CompileAsyncQuery استعلام LINQ مسبقًا إلى مفوّض. وتحدث الترجمة مرة واحدة عند بدء التشغيل؛ أما الاستدعاءات اللاحقة فتتجاوز خطوة الترجمة بالكامل.
// Define once, reuse many times
private static readonly Func<AppDbContext, int, Task<Product?>> GetProductById =
EF.CompileAsyncQuery(
(AppDbContext db, int id) =>
db.Products.AsNoTracking().FirstOrDefault(p => p.Id == id));
// Usage
var product = await GetProductById(_db, 42);الإسقاط باستخدام Select لتحسين الأداء
أسقط البيانات دائمًا إلى الحقول التي تحتاج إليها بالضبط. فاختيار DTO بدلًا من كيان كامل يقلل عدد الأعمدة التي يتم جلبها وحجم البيانات المنقولة عبر الشبكة والضغط على GC.
// Load entire entity (over-fetch)
var all = await _db.Products.ToListAsync();
// Projection: only fetch needed columns
var names = await _db.Products
.AsNoTracking()
.Select(p => new { p.Id, p.Name, p.Price })
.ToListAsync();
// SQL: SELECT Id, Name, Price FROM Productsتلميحات الفهارس وتحسين الاستعلامات الخام
يحترم EF Core فهارس قاعدة البيانات التي تهيئها باستخدام HasIndex(). وتؤثر الفهارس المركبة، والفهارس المصفاة، والأعمدة المضمّنة جميعًا في أداء خطة الاستعلام.
// Configure compound index in OnModelCreating
modelBuilder.Entity<Order>()
.HasIndex(o => new { o.CustomerId, o.Status })
.HasFilter("[Status] = 0") // filtered index
.IncludeProperties(o => o.PlacedAt);
// The query below will use this index efficiently
var pending = await _db.Orders
.AsNoTracking()
.Where(o => o.CustomerId == 5 && o.Status == OrderStatus.Pending)
.ToListAsync();تقسيم النتائج إلى صفحات باستخدام Skip وTake
قسّم مجموعات النتائج الكبيرة إلى صفحات دائمًا. إذ تُترجم Skip وTake إلى OFFSET/FETCH في SQL، ما يتجنب تحميل آلاف الصفوف في الذاكرة.
int page = 2, pageSize = 25;
var products = await _db.Products
.AsNoTracking()
.Where(p => p.IsActive)
.OrderBy(p => p.Name)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
// SQL: ... ORDER BY Name OFFSET 25 ROWS FETCH NEXT 25 ROWS ONLYالعد بكفاءة
استخدم CountAsync() أو AnyAsync() بدلًا من تحميل السجلات لمجرد عدّها. ويكون AnyAsync() أسرع من COUNT عندما تحتاج فقط إلى التحقق من وجود سجلات.
// Total count for pagination
int totalCount = await _db.Products
.Where(p => p.IsActive)
.CountAsync();
// Existence check — faster than Count > 0
bool hasExpensive = await _db.Products
.AnyAsync(p => p.Price > 1000m);
// SQL: IF EXISTS (SELECT 1 FROM ...) SELECT 1الإدراج الجماعي باستخدام AddRange
استخدم AddRange لإضافة كيانات متعددة دفعة واحدة. إذ يجمع EF Core عبارات INSERT في عدد أقل من الرحلات إلى قاعدة البيانات، ويتحكم المورّد في ذلك عبر MaxBatchSize.
var newProducts = Enumerable.Range(1, 1000)
.Select(i => new Product { Name = $"Product {i}", Price = i * 1.5m })
.ToList();
_db.Products.AddRange(newProducts);
await _db.SaveChangesAsync();
// Batched: typically 100-1000 rows per round-tripEF.Functions لدوال قاعدة البيانات
يعرِض EF.Functions دوال خاصة بقاعدة البيانات، مثل LIKE وأجزاء التاريخ والبحث في النص الكامل، ويترجمها EF Core إلى SQL بدلًا من تقييمها في الذاكرة.
// LIKE pattern search (SQL-side)
var results = await _db.Products
.Where(p => EF.Functions.Like(p.Name, "%keyboard%"))
.AsNoTracking()
.ToListAsync();
// Date difference (SQL-side)
var recentOrders = await _db.Orders
.Where(o => EF.Functions.DateDiffDay(o.PlacedAt, DateTime.UtcNow) <= 7)
.ToListAsync();تشخيص الاستعلامات البطيئة
فعّل تسجيل استعلامات EF Core لرؤية SQL المُنشأ. استخدم EnableSensitiveDataLogging() في بيئة التطوير لرؤية قيم المعلمات.
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlServer(connStr)
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging() // dev only!
.EnableDetailedErrors()); // dev only!عمليًا: نقطة نهاية قائمة محسّنة
تجمع نقطة نهاية قائمة جاهزة للإنتاج بين AsNoTracking والإسقاط والفهارس وتقسيم النتائج إلى صفحات لتحقيق أقصى أداء.
app.MapGet("/products", async (
AppDbContext db,
string? search,
int page = 1, int size = 20) =>
{
var query = db.Products
.AsNoTracking()
.Where(p => p.IsActive);
if (!string.IsNullOrEmpty(search))
query = query.Where(p => EF.Functions.Like(p.Name, $"%{search}%"));
var total = await query.CountAsync();
var items = await query
.OrderBy(p => p.Name)
.Skip((page - 1) * size).Take(size)
.Select(p => new { p.Id, p.Name, p.Price })
.ToListAsync();
return Results.Ok(new { total, items });
});تحقق سريع
ما الفائدة الرئيسية من EF.CompileAsyncQuery مقارنةً باستعلام LINQ عادي؟
مراجعة: الاستعلامات المترجمة والأداء
أهم النقاط:
AsNoTracking(): تجاوز متعقّب التغييرات في الاستعلامات التي تقتصر على القراءة — أكبر تحسين سهل وأسرعهEF.CompileAsyncQuery: ترجمة الاستعلامات الساخنة مسبقًا إلى مفوّضات- الإسقاط باستخدام
Select: جلب الأعمدة المطلوبة فقط - تحسّن الفهارس المناسبة، مثل
HasIndex، خطط الاستعلام بدرجة كبيرة - قسّم النتائج إلى صفحات باستخدام
Skip/Take؛ واستخدمAnyAsyncللتحقق من الوجود - فعّل تسجيل الاستعلامات في بيئة التطوير لاكتشاف SQL البطيء أو غير المتوقع
الأسئلة الشائعة
هل درس «الاستعلامات المجمّعة والأداء» مجاني؟
نعم — نص درس «الاستعلامات المجمّعة والأداء» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C# Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C# Academy 4 دروس في المجموع.
ماذا ستتعلم في «الاستعلامات المجمّعة والأداء»؟
حسّنوا أداء الاستعلامات باستخدام EF.CompileQuery وAsNoTracking والإسقاط مع Select. تتمرن على C# Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ C# Academy؟
لا تُشترط خبرة سابقة. C# Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «الاستعلامات المجمّعة والأداء»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس C# Academy هذا؟
نعم. كل درس في C# Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- التحميل المبكر والكسول والصريح
- SQL خام والإجراءات المخزّنة والاستيفاء
- الاستعلامات المجمّعة والأداء
- عوامل تصفية الاستعلام العامة والكيانات المملوكة