محتويات الدورة

ASP.NET Core / المتوسط: البيانات والهوية والعمل غير المتزامن / الدرس 11

EF Core وحدود الحفظ

راجع وحدة العمل، وترجمة الاستعلامات لدى الموفّر، والتتبع، والتزامن، والترحيلات، والمعاملات، دون اعتبار LINQ في الذاكرة اختبارا لقاعدة بيانات.

وقت القراءة التقريبي: 5 دقيقةتاريخ مراجعة المحتوى
في هذه الصفحة

أهداف الدرس

  • اشرح DbContext قصير العمر وغير الآمن للاستخدام المتزامن بين الخيوط.
  • افصل ترجمة IQueryable عن التعداد المحلي.
  • طابق اختبارات الحفظ مع الموفّر الفعلي وعقد المعاملات المقصود.

السياق وحدة عمل ذات ملكية واضحة

يتتبع DbContext حالة الكيانات وينسق العمليات التي ينفذها الموفّر خلال وحدة عمل قصيرة. وهو غير آمن للاستخدام المتزامن بين الخيوط، ويجب ألا يُشارك بين عمليات متوازية. انتظر الاستعلام غير المتزامن قبل استخدام السياق مرة أخرى، وتخلص منه بعد اكتمال العمل الذي يملكه. قد يناسب النطاق الافتراضي لكل طلب تطبيقات كثيرة، لكنه لا يحدد تلقائيا كل معاملة خلفية أو معاملة تضم عدة عمليات.

تجميع سياقات DbContext وتجميع اتصالات قاعدة البيانات آليتان مختلفتان. قد يعيد التجميع استخدام نسخ السياق، ولذلك يحتاج إلى عناية بالحالة المخصصة، مثل معلومات المستأجر. لا تستنتج هوية عمل جديدة من كائن أعيد استخدامه. اجعل إصدارات الموفّر والمحرك، وملكية الاتصال، ودورة الحياة صريحة.

لا تُنفّذ تهيئة الموفّر في هذه الدورة

يوضح المقتطف غير القابل للتشغيل موضع تسجيل الموفّر وDbContext. يحتاج إلى حزم EF والموفّر الصحيحة، ونموذج، وإعداد اتصال موثوق، ومحرك فعلي. لا تثبت الدورة هذه الحزم ولا تفتح قاعدة بيانات. لا يشكل سطر UseSqlServer يبدو معقولا دليلا على ترجمة SQL أو الأذونات أو المعاملات أو الترحيلات.

أبعد أسرار الاتصال عن المصدر والسجلات. ينبغي اختيار الموفّر المهيأ عن قصد ليلائم دلالات البيانات التي يحتاجها التطبيق. ليست SQLite وموفّر InMemory في EF والمحرك العلائقي المستخدم في الإنتاج بدائل اختبار متكافئة، حتى عندما تبدو أمثلة الإنشاء والقراءة والتحديث والحذف البسيطة متشابهة.

csharp تسجيل توضيحي لموفّر البيانات
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString));

لا يُترجم هذا المقتطف ولا يُشغّل؛ فموفّر EF ونموذج السياق والاتصال غائبة عن قصد.

قد يظل الاستعلام مؤجلا إلى ما بعد انتهاء السياق

يمثل IQueryable تعبيرات يفسرها الموفّر إلى أن يحين التنفيذ. قد يُنفّذ استعلام مؤجل أعيد إلى المستدعي خارج مدة حياة السياق، أو يتيح للمستدعين تركيب استعلامات غير محدودة. حوّل إسقاطا محدودا إلى نتائج فعلية ما دامت ملكية السياق سارية، ووثّق اختيار التتبع أو عدم التتبع، والترتيب، وتقسيم الصفحات، والإلغاء، ومستوى تفصيل صفوف النتيجة المتوقع.

تحدد AsEnumerable وعمليات تجسيد النتائج حدودا للتنفيذ قد تنقل العمل إلى الذاكرة. قد تكون دالة C# صالحة على قائمة لكنها غير قابلة للترجمة إلى SQL. ويمكن أن تغير عمليات Include والاستعلامات المقسمة وأنماط التحميل المسبق أو الكسول عدد الرحلات إلى قاعدة البيانات وتضاعف الصفوف. افحص سلوك الموفّر الفعلي باختبارات معتمدة بدلا من تخمينه من سلسلة الاستدعاءات المتتابعة.

يثبت اختبار الإسقاط المحلي الدلالات المحلية فقط

يرشح البرنامج الكامل سجلات مضمّنة ويسقطها إلى كائن DTO لواجهة API باستخدام LINQ to Objects. يتحقق من الشكل والترتيب المقصودين دون قاعدة بيانات. قد يصلح اختبارا ضيقا للمجال أو الإسقاط، لكنه لا يتحقق من سلوك null أو قواعد المقارنة اللغوية في SQL، أو الفهارس، أو عزل المعاملات، أو كلفة الاستعلام، أو ترجمة الموفّر.

تجنب تحميل جداول كاملة لمجرد سهولة الترشيح المحلي. حدد أحجام الصفحات وترتيبا مستقرا، وأسقط الحقول المطلوبة فقط ضمن نموذج الاستعلام الذي يدعمه الموفّر. قد تقلل قراءة دون تتبع عمل التتبع عندما تلائم الحالة، لكنها ليست ميزة للصلاحيات أو لأمان الاستخدام المتزامن بين الخيوط.

csharp اختبار إسقاط اصطناعي في الذاكرة
using System;
using System.Linq;

var rows = new[] { new Row(2, true), new Row(1, false), new Row(3, true) };
var result = rows.Where(row => row.Active).OrderBy(row => row.Id).Select(row => new ReportDto(row.Id)).ToArray();
Console.WriteLine(string.Join(",", result.Select(item => item.Id)));

internal sealed record Row(int Id, bool Active);
internal sealed record ReportDto(int Id);
الناتج المتوقع
2,3

ينتج التسلسل المحلي 2,3؛ ولا يُنفّذ استعلام EF أو محرك قاعدة بيانات.

لنجاح الحفظ نطاق معاملات محدد

يمكن أن توفر SaveChanges سلوكا ذريّا للتغييرات التي تحفظها عندما يدعم الموفّر المعاملات، لكنها ليست معاملة تشمل كل استدعاء HTTP أو رسالة أو أثر خارجي. قد تكتشف رموز التزامن التفاؤلي الكتابات المتقادمة، وتحتاج إلى سياسة موثقة للتعارض وإعادة المحاولة. وقد تكرر الإعادة العمياء لعملية غير آمنة التكرار وذات نتيجة غير مؤكدة آثارها.

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

الأخطاء الشائعة وإصلاحها

يُستخدم DbContext واحد في أعمال طلب متوازية.

لماذا يحدث؟
تُشارك وحدة عمل غير آمنة للاستخدام المتزامن بين الخيوط.
كيف تصلحه؟
استخدم سياقات أو نطاقات منفصلة ذات ملكية واضحة، أو انتظر أعمال الوحدة المقصودة بالتتابع.
كيف تتجنب تكراره؟
اختبر افتراضات مدة الحياة والتزامن.

يُقدّم اختبار قائمة بوصفه تحققا من موفّر SQL.

لماذا يحدث؟
اختلط التنفيذ المحلي بالتنفيذ لدى الموفّر.
كيف تصلحه؟
استخدم الموفّر الفعلي لادعاءات قاعدة البيانات ضمن عزل معتمد.
كيف تتجنب تكراره؟
صف نطاق اختبارات الوحدة والإسقاط بدقة، دون توسيع ما تثبته.

يتسرب استعلام مؤجل غير محدود خارج الطلب.

لماذا يحدث؟
لا توجد سيطرة على التنفيذ وملكية السياق.
كيف تصلحه؟
اعرض عقدا محدودا للعملية ونتيجتها.
كيف تتجنب تكراره؟
اختبر توقيت تجسيد النتائج وحجمها.

اختبر فهمك

1. لماذا قد ينجح اختبار بموفّر InMemory في EF بينما يفشل على محرك علائقي؟

اعرض الحل المشروح

لا يعيد هذا الموفّر إنتاج جميع القيود العلائقية أو ترجمة SQL أو سلوك المقارنة اللغوية وnull أو المعاملات أو دلالات التزامن لدى الموفّر المستهدف. قد يفيد في اختبار محدد النطاق بعناية، لكنه لا يصادق على سلوك قاعدة البيانات. استخدم الموفّر المقصود ضمن عزل معتمد لهذه الادعاءات، وأبق اختبارات المجال مستقلة عن افتراضات الموفّر حيث يمكن ذلك.

2. يحدث سياق صفا ويرسل رسالة خارجية. هل تجعل SaveChanges الأثرين ذريين معا؟

اعرض الحل المشروح

لا. لا يشمل نطاق معاملتها تلقائيا وسيط رسائل أو خدمة HTTP خارجية. حدد نمط outbox أو تصميما آخر مبررا للاتساق والتسوية، مع منع تكرار الأثر وسياسة إعادة المحاولة. اختبر التعطل حول كل حد تثبيت. لا يثبت نجاح الحفظ وحده أن الرسالة أرسلت مرة واحدة، أو أن الإعادة لن تكرر عملية العمل.

أهم ما تعلمته

  • DbContext وحدة عمل قصيرة ذات ملكية واضحة، وليس خدمة مشتركة للعمل المتوازي.
  • تحتاج ترجمة الموفّر وLINQ المحلية إلى أدلة تحقق مختلفة.
  • تتطلب المعاملات والتزامن والترحيلات والآثار الخارجية حدودا صريحة.

المراجع الرسمية