c# - كيان إطار الاستعلام المتزامن




entity-framework async-await (2)

أعمل على بعض عناصر Web API باستخدام Entity Framework 6 وأحد أساليب التحكم الخاصة بي هي "Get All" التي تتوقع استلام محتويات جدول من قاعدة البيانات الخاصة بي كـ IQueryable<Entity> . في المستودع الخاص بي ، أتساءل عما إذا كان هناك أي سبب مفيد للقيام بذلك بشكل غير متزامن لأنني جديد على استخدام EF مع المزامنة.

أساسا أنه يتلخص في

 public async Task<IQueryable<URL>> GetAllUrlsAsync()
 {
    var urls = await context.Urls.ToListAsync();
    return urls.AsQueryable();
 }

ضد

 public IQueryable<URL> GetAllUrls()
 {
    return context.Urls.AsQueryable();
 }

هل ستحصل النسخة المتزامنة بالفعل على مزايا أداء هنا أم هل أتحمل تكاليف غير ضرورية من خلال عرض قائمة أولاً (باستخدام مزامنة العقل) ثم الانتقال إلى IQueryable؟

https://code.i-harness.com


هناك اختلاف كبير في المثال الذي نشرته ، الإصدار الأول:

var urls = await context.Urls.ToListAsync();

هذا أمر سيئ ، فهو يقوم في الأساس select * from table ، ويعيد جميع النتائج إلى الذاكرة ، ثم يطبق where مقابل ذلك في مجموعة الذاكرة بدلاً من select * from table where... مقابل قاعدة البيانات.

لن تضرب الطريقة الثانية فعليًا قاعدة البيانات حتى يتم تطبيق استعلام على IQueryable (ربما عبر .Where().Select() عملية نمط .Where().Select() التي ستُرجع فقط قيم db التي تطابق الاستعلام.

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

ومع ذلك ، فإن الاختلاف الرئيسي (والفائدة) هو أن إصدار async يسمح بمزيد من الطلبات المتزامنة لأنه لا يحظر مؤشر ترابط المعالجة أثناء انتظار IO لإكماله (استعلام db ، الوصول إلى الملفات ، طلب الويب وما إلى ذلك).


يبدو أن المشكلة تكمن في سوء فهمك لكيفية عمل المزامنة / الانتظار مع Entity Framework.

حول الكيان الإطار

لذلك ، دعونا ننظر إلى هذا الرمز:

public IQueryable<URL> GetAllUrls()
{
    return context.Urls.AsQueryable();
}

ومثال ذلك الاستخدام:

repo.GetAllUrls().Where(u => <condition>).Take(10).ToList()

ماذا يحدث هناك؟

  1. نحصل على كائن repo.GetAllUrls() إلى قاعدة البيانات حتى الآن) باستخدام repo.GetAllUrls()
  2. نقوم بإنشاء كائن .Where(u => <condition> جديد مع شرط محدد باستخدام .Where(u => <condition>
  3. نقوم بإنشاء كائن IQueryable جديد مع حد ترحيل محدد باستخدام .Take(10)
  4. .ToList() النتائج من قاعدة البيانات باستخدام .ToList() . يتم تجميع كائن IQueryable بنا إلى sql (مثل select top 10 * from Urls where <condition> ). وقاعدة البيانات يمكن أن تستخدم الفهارس ، خادم sql يرسل لك 10 كائنات فقط من قاعدة البيانات الخاصة بك (وليس كل مليار عنوان URL المخزن في قاعدة البيانات)

حسنًا ، لنلقِ نظرة على الكود الأول:

public async Task<IQueryable<URL>> GetAllUrlsAsync()
{
    var urls = await context.Urls.ToListAsync();
    return urls.AsQueryable();
}

مع نفس مثال الاستخدام ، حصلنا على:

  1. نحن نقوم بتحميل في ذاكرة جميع عناوين URL المليار المخزنة في قاعدة البيانات الخاصة بك باستخدام await context.Urls.ToListAsync(); .
  2. حصلنا على تجاوز الذاكرة. الطريقة الصحيحة لقتل الخادم الخاص بك

حول المتزامن / تنتظر

لماذا المزامنة / الانتظار مفضل استخدامها؟ لنلقِ نظرة على هذا الكود:

var stuff1 = repo.GetStuff1ForUser(userId);
var stuff2 = repo.GetStuff2ForUser(userId);
return View(new Model(stuff1, stuff2));

ماذا يحدث هنا؟

  1. ابتداءً من السطر 1 var stuff1 = ...
  2. نرسل الطلب إلى خادم sql الذي نريد الحصول على بعض الاشياء 1 ل userId
  3. ننتظر (الموضوع الحالي محظور)
  4. ننتظر (الموضوع الحالي محظور)
  5. .....
  6. خادم مزود ترسل لنا استجابة
  7. ننتقل إلى السطر 2 var stuff2 = ...
  8. نرسل الطلب إلى خادم sql الذي نريد الحصول على بعض الاشياء 2 ل userId
  9. ننتظر (الموضوع الحالي محظور)
  10. ومره اخرى
  11. .....
  12. خادم مزود ترسل لنا استجابة
  13. نحن تقديم الرأي

لذلك دعونا ننظر إلى نسخة غير متزامنة منه:

var stuff1Task = repo.GetStuff1ForUserAsync(userId);
var stuff2Task = repo.GetStuff2ForUserAsync(userId);
await Task.WhenAll(stuff1Task, stuff2Task);
return View(new Model(stuff1Task.Result, stuff2Task.Result));

ماذا يحدث هنا؟

  1. نرسل الطلب إلى خادم sql للحصول على stuff1 (السطر 1)
  2. نرسل الطلب إلى خادم sql للحصول على stuff2 (السطر 2)
  3. ننتظر الردود من خادم sql ، لكن سلسلة الرسائل الحالية غير محظورة ، يمكنه التعامل مع الاستفسارات من مستخدمين آخرين
  4. نحن تقديم الرأي

الطريقة الصحيحة للقيام بذلك

كود جيد جدا هنا:

using System.Data.Entity;

public IQueryable<URL> GetAllUrls()
{
   return context.Urls.AsQueryable();
}

public async Task<List<URL>> GetAllUrlsByUser(int userId) {
   return await GetAllUrls().Where(u => u.User.Id == userId).ToListAsync();
}

ملاحظة ، مما يجب إضافته using System.Data.Entity أجل استخدام الأسلوب ToListAsync() ل ToListAsync() .

لاحظ أنه إذا كنت لا تحتاج إلى التصفية والترحيل والأشياء ، فلن تحتاج إلى التعامل مع IQueryable . يمكنك فقط استخدام await context.Urls.ToListAsync() والعمل مع List<Url> .





async-await