تصميم طبقة تجريد قاعدة البيانات - باستخدام iRepository بالطريقة الصحيحة؟

StackOverflow https://stackoverflow.com/questions/2347337

سؤال

أنا بصدد تصميم تطبيق ASP.NET MVC وركضت عبر بعض الأفكار المثيرة للاهتمام.

العديد من العينات التي رأيتها تصف واستخدام نمط المستودع (IRepository) لذلك هذه هي الطريقة التي فعلت بها أثناء تعلم MVC.

الآن أعرف ما يفعله كل شيء ، بدأت في النظر إلى تصميمي الحالي وأتساءل عما إذا كانت أفضل طريقة للذهاب.

حاليا لدي أساسية IUserRepository, التي تحدد طرق مثل FindById(), SaveChanges(), ، إلخ.

حاليًا ، كلما أردت تحميل/الاستعلام عن جدول المستخدم في DB ، أفعل شيئًا على غرار ما يلي:

    private IUserRepository Repository;

    public UserController()
        : this(new UserRepository())
    { }

    [RequiresAuthentication]
    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Edit(string ReturnUrl, string FirstRun)
    {
        var user = Repository.FindById(User.Identity.Name);

        var viewModel = Mapper.Map<User, UserEditViewModel>(user);
        viewModel.FirstRun = FirstRun == "1" ? true : false;

        return View("Edit", viewModel);
    }

    [AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken(Salt = "SaltAndPepper")]
    public ActionResult Edit(UserEditViewModel viewModel, string ReturnUrl)
    {
        //Map the ViewModel to the Model
        var user = Repository.FindById(User.Identity.Name);

        //Map changes to the user
        Mapper.Map<UserEditViewModel, User>(viewModel, user);

        //Save the DB changes
        Repository.SaveChanges();

        if (!string.IsNullOrEmpty(ReturnUrl))
            return Redirect(ReturnUrl);
        else
            return RedirectToAction("Index", "User");
    }

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

لقد وجدت سؤالًا رائعًا بشأن استخدام واجهة مستودع عام IRepository<T> هنا ويبدو أيضًا فكرة ثابتة RepositoryFactory على عدد من المدونات. في الأساس ، يتم الاحتفاظ بمثيل واحد فقط من المستودع على الإطلاق ويتم الحصول عليه عبر هذا المصنع

لذا فإن سؤالي يدور حول كيفية قيام الناس بذلك في تطبيقات ، وما يعتبر ممارسة جيدة.

هل لدى الناس مستودعات فردية بناءً على كل جدول (IUserRepository)?
هل يستخدمون عام IRepository<T>?
هل يستخدمون مصنع مستودع ثابت؟
أو شيء آخر تماما؟

تحرير: لقد أدركت للتو أنني ربما يجب أن أسأل أيضًا:

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

Bounty Edit: لقد بدأت مكافأة للحصول على المزيد من وجهات النظر (ليس أن تيم لم يكن مفيدًا).

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

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

المحلول

بعض المشكلات الواضحة للغاية مع فكرة عام IRepository<T>:

  • يفترض أن كل كيان يستخدم نفس النوع من المفاتيح ، وهو أمر غير صحيح في أي نظام غير تميز تقريبًا. ستستخدم بعض الكيانات GUIDS ، والبعض الآخر قد يكون لدى نوع من المفتاح الطبيعي و/أو المركب. يمكن لـ Nhibernate دعم هذا بشكل جيد إلى حد ما ، لكن LINQ لـ SQL سيء للغاية في ذلك - عليك أن تكتب قدرًا كبيرًا من التعليمات البرمجية المتسللة للقيام بتخطيط مفاتيح تلقائي.

  • وهذا يعني أن كل مستودع لا يمكنه التعامل مع نوع كيان واحد فقط ويدعم فقط العمليات التافهة فقط. عندما يتم ترحيل مستودع إلى هذا الغلاف البسيط Crud ، فإنه لا يستخدم سوى القليل جدًا على الإطلاق. قد تقوم أيضًا بتسليم العميل IQueryable<T> أو Table<T>.

  • يفترض أنك تقوم بنفس العمليات بالضبط على كل كيان. في الواقع سيكون هذا بعيدًا جدًا عن الحقيقة. بالتأكيد، يمكن تريد الحصول على ذلك Order من خلال هويته ، ولكن على الأرجح تريد الحصول على قائمة من Order كائنات لعميل معين وضمن نطاق تاريخ. فكرة عامة تماما IRepository<T> لا يسمح بحقيقة أنك ستحتاج بالتأكيد إلى أداء أنواع مختلفة من الاستعلامات على أنواع مختلفة من الكيانات.

الهدف الكامل من نمط المستودع هو إنشاء التجريد على أنماط الوصول إلى البيانات الشائعة. أعتقد أن بعض المبرمجين يشعرون بالملل من إنشاء مستودعات حتى يقولوا "مهلا ، أعرف ، سأقوم بإنشاء über-rebository يمكنه التعامل مع أي نوع كيان!" وهو أمر رائع باستثناء أن المستودع غير مجدي إلى حد كبير بالنسبة إلى 80 ٪ مما تحاول القيام به. لا بأس بمثابة فئة/واجهة أساسية ، ولكن إذا كان هذا هو المدى الكامل للعمل الذي تقوم به ، فأنت مجرد كسول (وتضمن الصداع في المستقبل).


من الناحية المثالية ، قد أبدأ بمستودع عام يشبه هذا:

public interface IRepository<TKey, TEntity>
{
    TEntity Get(TKey id);
    void Save(TEntity entity);
}

ستلاحظ أن هذا لا عند List أو GetAll الوظيفة - هذا لأنه من العبث الاعتقاد بأنه من المقبول استرداد البيانات من جدول كامل في وقت واحد في الرمز. هذا عندما تحتاج إلى البدء في الذهاب إلى مستودعات محددة:

public interface IOrderRepository : IRepository<int, Order>
{
    IEnumerable<Order> GetOrdersByCustomer(Guid customerID);
    IPager<Order> GetOrdersByDate(DateTime fromDate, DateTime toDate);
    IPager<Order> GetOrdersByProduct(int productID);
}

وهكذا - تحصل على الفكرة. وبهذه الطريقة ، لدينا مستودع "عام" إذا كنا بحاجة فعليًا إلى دلالات الاسترداد البسيطة بشكل لا يصدق ، ولكن بشكل عام لن نمرر ذلك أبدًا ، وبالتأكيد ليس إلى فئة تحكم.


الآن أما بالنسبة لوحدات التحكم ، عليك القيام بذلك بشكل صحيح ، وإلا فقد ألغت كل العمل الذي قمت به للتو في تجميع جميع المستودعات.

يحتاج وحدة التحكم إلى خذ مستودعها من العالم الخارجي. السبب في إنشاء هذه المستودعات هو حتى تتمكن من القيام بنوع من الانعكاس للسيطرة. هدفك النهائي هنا هو أن تكون قادرًا على تبديل مستودع لمجلة أخرى - على سبيل المثال ، لإجراء اختبار الوحدة ، أو إذا قررت التبديل من LINQ إلى SQL إلى إطار الكيان في مرحلة ما في المستقبل.

مثال على هذا المبدأ هو:

public class OrderController : Controller
{
    public OrderController(IOrderRepository orderRepository)
    {
        if (orderRepository == null)
            throw new ArgumentNullException("orderRepository");
        this.OrderRepository = orderRepository;
    }

    public ActionResult List(DateTime fromDate, DateTime toDate) { ... }
    // More actions

    public IOrderRepository OrderRepository { get; set; }
}

بمعنى آخر تمتلك وحدة التحكم لا فكرة عن كيفية إنشاء مستودع, ، ولا ينبغي. إذا كان لديك أي بناء مستودع مستمر ، فإنه يخلق اقترانًا لا تريده حقًا. السبب في أن وحدات تحكم عينة ASP.NET MVC لديها منشئي بدون معلمات يخلق مستودعات ملموسة هو أن المواقع تحتاج إلى أن تكون قادرة على التجميع والتشغيل دون إجبارك على إعداد إطار حقن التبعية بالكامل.

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

[TestMethod]
public void Can_add_order()
{
    OrderController controller = new OrderController();
    FakeOrderRepository fakeRepository = new FakeOrderRepository();
    controller.OrderRepository = fakeRepository; //<-- Important!
    controller.SubmitOrder(...);
    Assert.That(fakeRepository.ContainsOrder(...));
}

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


هذا ليس di بعد ، مانع لك ، هذا مجرد مزيف/السخرية. عندما يأتي DI إلى الصورة هو عندما تقرر أن LINQ إلى SQL لا تفعل ما يكفي من أجلك وتريد حقًا HQL في NHIBERNATE ، لكن الأمر سيستغرق 3 أشهر لتنفيذ كل شيء ، وتريد أن تكون قادرًا على ذلك افعل هذا مستودع واحد في وقت واحد. لذلك ، على سبيل المثال ، باستخدام إطار عمل DI تسعة, ، كل ما عليك فعله هو تغيير هذا:

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<LinqToSqlOrderRepository>();
Bind<IProductRepository>().To<LinqToSqlProductRepository>();

ل:

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<NHibernateOrderRepository>();
Bind<IProductRepository>().To<NHibernateProductRepository>();

وهناك ، الآن كل ما يعتمد عليه IOrderRepository يستخدم إصدار nhibernate ، عليك فقط التغيير خط واحد من الكود بدلاً من مئات الخطوط المحتملة. ونقوم بتشغيل LINQ إلى SQL و Nhibernate إصدارات جنبًا إلى جنب ، ونحن ننقل وظائف على قطعة قطعة دون كسر أي شيء في الوسط.


لتلخيص جميع النقاط التي قمت بها:

  1. لا تعتمد بشكل صارم على عام IRepository<T> واجهه المستخدم. معظم الوظائف التي تريدها من المستودع محدد, ، ليس نوعي. إذا كنت تريد تضمين IRepository<T> في المستويات العليا من التسلسل الهرمي للفئة/الواجهة ، هذا جيد ، ولكن يجب أن تعتمد وحدات التحكم على محدد المستودعات حتى لا ينتهي بك الأمر إلى تغيير الكود في 5 أماكن مختلفة عندما تجد أن المستودع العام يفتقد الطرق المهمة.

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

  3. عادةً ما ترغب في توصيل وحدات التحكم باستخدام إطار حقن التبعية ، ويمكن دمج الكثير منها بسلاسة مع ASP.NET MVC. إذا كان هذا أكثر من اللازم بالنسبة لك ، فعليك على الأقل يجب أن تستخدم نوعًا من مزود الخدمة الثابتة حتى تتمكن من تركيز جميع منطق إنشاء المستودعات. (على المدى الطويل ، من المحتمل أن تجد أنه من الأسهل تعلم واستخدام إطار عمل DI).

نصائح أخرى

هل لدى الأشخاص مستودعات فردية تعتمد على كل جدول (iuserrepository)؟أميل إلى الحصول على مستودع لكل إجمالي ، وليس كل جدول.

هل يستخدمون irepository عام؟إذا أمكن ، نعم

هل يستخدمون مصنع مستودع ثابت؟أفضل حقن مثيل مستودع من خلال حاوية IOC

إليكم كيف أستخدمه. أنا أستخدم iRepository لجميع العمليات الشائعة في جميع مستودعاتي.

public interface IRepository<T> where T : PersistentObject
{
    T GetById(object id);
    T[] GetAll();
    void Save(T entity);
}

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

public interface IUserRepository : IRepository<User>
{
    User GetByUserName(string username);
}

سيبدو التنفيذ هكذا:

public class UserRepository : RepositoryBase<User>, IUserRepository
{
    public User GetByUserName(string username)
    {
        ISession session = GetSession();
        IQuery query = session.CreateQuery("from User u where u.Username = :username");
        query.SetString("username", username);

        var matchingUser = query.UniqueResult<User>();

        return matchingUser;
    }
}


public class RepositoryBase<T> : IRepository<T> where T : PersistentObject
{
    public virtual T GetById(object id)
    {
        ISession session = GetSession();
        return session.Get<T>(id);
    }

    public virtual T[] GetAll()
    {
        ISession session = GetSession();
        ICriteria criteria = session.CreateCriteria(typeof (T));
        return criteria.List<T>().ToArray();
    }

    protected ISession GetSession()
    {
        return new SessionBuilder().GetSession();
    }

    public virtual void Save(T entity)
    {
        GetSession().SaveOrUpdate(entity);
    }
}

سيبدو ما في USERCONTROLLER على النحو التالي:

public class UserController : ConventionController
{
    private readonly IUserRepository _repository;
    private readonly ISecurityContext _securityContext;
    private readonly IUserSession _userSession;

    public UserController(IUserRepository repository, ISecurityContext securityContext, IUserSession userSession)
    {
        _repository = repository;
        _securityContext = securityContext;
        _userSession = userSession;
    }
}

من المستودع يتم إنشاء مثيل له باستخدام نمط حقن التبعية باستخدام مصنع تحكم مخصص. أنا استخدم بنية كما طبقة حقن التبعية.

طبقة قاعدة البيانات هي nhibernate. Isession هي بوابة قاعدة البيانات في هذه الجلسة.

أنا أقترح عليك أن تنظر Codecampserver الهيكل ، يمكنك أن تتعلم الكثير منه.

مشروع آخر يمكنك تعلمه من يستطيع مساعدتي. وهو ما لا أحفر فيه بعد ذلك.

هل لدى الأشخاص مستودعات فردية تعتمد على كل جدول (iuserrepository)؟

نعم ، كان هذا هو الخيار الأفضل لسببين:

  • يعتمد DAL الخاص بي على LINQ-to-SQL (لكن DTOs الخاصة بي هي واجهات تعتمد على كيانات LTS)
  • العمليات المنجزة الذري (إضافة عملية ذرية ، فإن الادخار آخر ، وما إلى ذلك)

هل يستخدمون irepository عام؟

نعم، حصريا, ، مستوحى من مخطط / قيمة DDD ، قمت بإنشاء irepositoryentity / irepositoryvalue و irepository عام لأشياء أخرى.

هل يستخدمون مصنع مستودع ثابت؟

نعم ولا: أنا أستخدم حاوية IOC استدعى عبر فئة ثابتة. حسنًا ... يمكننا أن نقول إنه نوع من المصنع.

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

يمكنك أن تجد ممتازة عام repopsitory المكتبة التي تمت كتابتها لتمكينها لاستخدامها كـ WebForms ASP: ObjectDataSource على codeplex: MultitierlinqtoSql

كل من وحدات التحكم الخاصة بي لديها مستودعات خاصة للإجراءات التي يحتاجون إليها لدعمها.

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