سؤال

لدي مستودعات (على سبيل المثال.ContactRepository وUserRepository وما إلى ذلك) والتي تتضمن الوصول إلى البيانات إلى نموذج المجال.

عندما كنت أنظر إلى البحث عن البيانات, ، على سبيل المثال.

  • العثور على جهة اتصال يبدأ الاسم الأول مع XYZ
  • جهة اتصال عيد ميلاده بعد عام 1960

    (إلخ)،

لقد بدأت في تنفيذ أساليب المستودع مثل FirstNameStartsWith (بادئة السلسلة) و أصغر من سنة الميلاد (السنة الدولية), ، بشكل أساسي باتباع الأمثلة العديدة الموجودة هناك.

ثم واجهت مشكلة - ماذا لو اضطررت إلى الجمع بين عمليات بحث متعددة؟كل طريقة من طرق البحث في المستودع، كما هو مذكور أعلاه، لا تُرجع سوى مجموعة محدودة من كائنات المجال الفعلية.بحثًا عن طريقة أفضل، بدأت الكتابة طرق التمديد على IQueryable<T>، على سبيل المثال.هذا:

public static IQueryable<Contact> FirstNameStartsWith(
               this IQueryable<Contact> contacts, String prefix)
{
    return contacts.Where(
        contact => contact.FirstName.StartsWith(prefix));
}        

الآن يمكنني أن أفعل أشياء مثل

ContactRepository.GetAll().FirstNameStartsWith("tex").YoungerThanBirthYear(1960);

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

فهل هذه هي الطريقة حقا للقيام بذلك، أم أن هناك طريقة أفضل لتحقيق نفس الهدف؟

هل كانت مفيدة؟

المحلول

@Alex - أعلم أن هذا سؤال قديم، ولكن ما سأفعله هو السماح للمستودع بالقيام بذلك أشياء بسيطة حقا فقط.وهذا يعني الحصول على كافة السجلات الخاصة بالجدول أو العرض.

ثم، في طبقة الخدمات (أنت تستخدم حلاً ذو طبقات n، أليس كذلك؟:)) سأتعامل مع جميع عناصر الاستعلام "الخاصة" هناك.

طيب وقت المثال

طبقة المستودع

ContactRepository.cs

public IQueryable<Contact> GetContacts()
{
    return (from q in SqlContext.Contacts
            select q).AsQueryable();
}

لطيفة وبسيطة. SqlContext هو المثال الخاص بك EF Context ..الذي لديه Entity عليه دعا Contacts ..والتي هي في الأساس فئة جهات اتصال SQL الخاصة بك.

هذا يعني أن هذه الطريقة تقوم في الأساس بما يلي: SELECT * FROM CONTACTS ...لكنها لا تصل إلى قاعدة البيانات بهذا الاستعلام ..إنه مجرد استفسار الآن.

نعم ..الطبقة التالية.. ركلة ...إلى الأعلى نذهب (بداية أي واحد؟)

طبقة الخدمات

ContactService.cs

public  ICollection<Contact> FindContacts(string name)
{
    return FindContacts(name, null)
}

public ICollection<Contact> FindContacts(string name, int? year)
{
   IQueryable<Contact> query = _contactRepository.GetContacts();

   if (!string.IsNullOrEmpty(name))
   {
       query = from q in query
               where q.FirstName.StartsWith(name)
               select q;
   }

   if (int.HasValue)
   {
       query = from q in query
               where q.Birthday.Year <= year.Value
               select q);
    }

    return (from q in query
            select q).ToList();
}

منتهي.

لذلك دعونا نلخص.أولا نبدأ بأمر بسيطاحصل على كل شيء من جهات الاتصال' استفسار.الآن، إذا كان لدينا اسمًا مقدمًا، فلنضيف مرشحًا لتصفية جميع جهات الاتصال حسب الاسم.بعد ذلك، إذا كان لدينا سنة مقدمة، فإننا نقوم بتصفية تاريخ الميلاد حسب السنة.إلخ.أخيرًا، نضغط على قاعدة البيانات (مع هذا الاستعلام المعدل) ونرى النتائج التي سنحصل عليها.

ملحوظات:-

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

نقاط الوجبات الجاهزة

  • تتعامل طبقة الخدمات مع جميع العناصر الذكية.هذا هو المكان الذي تقرر فيه البيانات التي تحتاجها.
  • المستودع عبارة عن SELECT * FROM TABLE بسيط أو INSERT/UPDATE بسيط في TABLE.

حظ سعيد :)

نصائح أخرى

لقد كنت أفكر في هذا الأمر كثيرًا مؤخرًا، بعد أن بدأت في وظيفتي الحالية.أنا معتاد على المستودعات، فهي تتبع مسار IQueryable الكامل باستخدام المستودعات الأساسية فقط كما تقترح.

أشعر أن نمط الريبو سليم ويقوم بعمل شبه فعال في وصف الطريقة التي تريد بها العمل مع البيانات الموجودة في مجال التطبيق.لكن المشكلة التي تصفها تحدث بالتأكيد.يصبح الأمر فوضويًا وسريعًا ويتجاوز مجرد تطبيق بسيط.

هل هناك، ربما، طرق لإعادة التفكير في سبب طلبك للبيانات بعدة طرق؟إذا لم يكن الأمر كذلك، فأنا أشعر حقًا أن النهج المختلط هو أفضل طريقة للمضي قدمًا.قم بإنشاء طرق الريبو للأشياء التي تعيد استخدامها.الأشياء التي في الواقع من المنطقي ل.جاف وكل ذلك.لكن تلك لمرة واحدة؟لماذا لا تستفيد من IQueryable والأشياء المثيرة التي يمكنك القيام بها به؟من السخافة، كما قلت، إنشاء طريقة لذلك، لكن هذا لا يعني أنك لا تحتاج إلى البيانات.DRY لا ينطبق حقًا هناك، أليس كذلك؟

قد يتطلب الأمر الانضباط للقيام بذلك بشكل جيد، لكنني أعتقد حقًا أنه المسار المناسب.

أدرك أن هذا أمر قديم، لكنني كنت أتعامل مع نفس المشكلة مؤخرًا، وتوصلت إلى نفس النتيجة التي توصلت إليها تشاد:مع القليل من الانضباط، يبدو أن مزيجًا من طرق الإرشاد وأساليب المستودع يعمل بشكل أفضل.

بعض القواعد العامة التي كنت أتبعها في تطبيق (Entity Framework):

ترتيب الاستعلامات

إذا كانت الطريقة تستخدم للطلب فقط، أفضل أن أكتب طرق التمديد التي تعمل عليها IQueryable<T> أو IOrderedQueryable<T> (للاستفادة من الموفر الأساسي.) على سبيل المثال

public static IOrderedQueryable<TermRegistration> ThenByStudentName(
    this IOrderedQueryable<TermRegistration> query)
{
    return query
        .ThenBy(reg => reg.Student.FamilyName)
        .ThenBy(reg => reg.Student.GivenName);
}

الآن يمكنني استخدام ThenByStudentName() حسب الحاجة داخل فئة المستودع الخاص بي.

الاستعلامات ترجع حالات فردية

إذا كانت الطريقة تتضمن الاستعلام باستخدام معلمات بدائية، فإنها تتطلب عادةً ObjectContext ولا يمكن صنعها بسهولة static.هذه الطرق التي أتركها في مستودعي، على سبيل المثال

public Student GetById(int id)
{
    // Calls context.ObjectSet<T>().SingleOrDefault(predicate) 
    // on my generic EntityRepository<T> class 
    return SingleOrDefault(student => student.Active && student.Id == id);
}

ومع ذلك، إذا كانت الطريقة تتضمن الاستعلام عن ملف EntityObject باستخدام لها خصائص الملاحة, ، ويمكن عادة أن يتم ذلك static بسهولة تامة، ويتم تنفيذها كوسيلة تمديد. على سبيل المثال

public static TermRegistration GetLatestRegistration(this Student student)
{
    return student.TermRegistrations.AsQueryable()
        .OrderByTerm()
        .FirstOrDefault();
}

الآن أستطيع أن أكتب بسهولة someStudent.GetLatestRegistration() دون الحاجة إلى مثيل مستودع في النطاق الحالي.

استعلامات إرجاع المجموعات

إذا كانت الطريقة ترجع بعض IEnumerable, ICollection أو IList, ، فأنا أحب أن أفعل ذلك static إذا كان ذلك ممكنا، واتركه على المستودع حتى لو كان يستخدم خصائص التنقل. على سبيل المثال

public static IList<TermRegistration> GetByTerm(Term term, bool ordered)
{
    var termReg = term.TermRegistrations;
    return (ordered)
        ? termReg.AsQueryable().OrderByStudentName().ToList()
        : termReg.ToList();
}

هذا لأن بلدي GetAll() الأساليب موجودة بالفعل في المستودع، وتساعد على تجنب الفوضى المزدحمة لطرق الامتداد.

سبب آخر لعدم تنفيذ "حروف المجموعة" هذه كطرق تمديد هو أنها تتطلب المزيد من التسمية المطولة لتكون ذات معنى، نظرًا لأن نوع الإرجاع غير ضمني.على سبيل المثال، المثال الأخير سيصبح GetTermRegistrationsByTerm(this Term term).

آمل أن يساعد هذا!

بعد مرور ست سنوات، أنا متأكد من أن @Alex قد حل مشكلته، ولكن بعد قراءة الإجابة المقبولة أردت إضافة سنتي.

الغرض العام من التوسع IQueryable مجموعات في أ مخزن لتوفير المرونة وتمكين المستهلكين من تخصيص استرجاع البيانات.ما فعله أليكس بالفعل هو عمل جيد.

الدور الأساسي أ طبقة الخدمة هو الالتزام فصل المخاوف المبدأ والعنوان منطق الأوامر المرتبطة بوظيفة الأعمال.

في تطبيقات العالم الحقيقي، منطق الاستعلام غالبًا لا يحتاج إلى أي تمديد يتجاوز آليات الاسترجاع التي يوفرها المستودع نفسه (على سبيل المثال.تعديلات القيمة، تحويلات النوع).

خذ بعين الاعتبار السيناريوهين التاليين:

IQueryable<Vehicle> Vehicles { get; }

// raw data
public static IQueryable<Vehicle> OwnedBy(this IQueryable<Vehicle> query, int ownerId)
{
    return query.Where(v => v.OwnerId == ownerId);
}

// business purpose
public static IQueryable<Vehicle> UsedThisYear(this IQueryable<Vehicle> query)
{
    return query.Where(v => v.LastUsed.Year == DateTime.Now.Year);
}

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

الاعتبارات الأساسية هي (أ) الغرض الأساسي من مستودعك و(ب) مدى رغبتك في الالتزام به CQRS و د.د.د الفلسفات.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top