Якщо шаблон сховища є надмірним для сучасних ОРМ (EF, nHibernate), що таке краща абстракція?


12

Нещодавно я прочитав багато аргументів проти використання шаблону репозиторію з потужним ORM, як Entity Framework, оскільки він містить функціонал схожих на сховища, а також функціонал Unit of Work.

Інший аргумент проти використання шаблону для такої ситуації, як тестування одиниць, полягає в тому, що модель сховища є протікаючою абстракцією, оскільки більш загальна реалізація використовує IQueryable.

Аргументи проти використання шаблону репозиторію мають сенс для мене, але запропоновані альтернативні методи абстракцій часто є більш заплутаними і виглядають так само непомірно, як проблема.

Рішення Джиммі Богардса, здається, являє собою суміш видувних абстракцій, але також впровадження власної архітектури. https://lostechies.com/jimmybogard/2012/10/08/favor-query-objects-over-repositories/

Ще один приклад того, що репозиторії є зайвими .... але використовуйте мою архітектуру! http://blog.gauffin.org/2012/10/22/griffin-decoupled-the-queries/

Ще ... http://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework

Я не знайшов чіткої заміни або альтернативи «надмірно складному» підходу шаблону сховища, який не є самим архітектурним.


4
Чого конкретно ви намагаєтесь досягти? Абстракції повинні мати мету. Якщо ви пишете додаток CRUD, ORM, ймовірно, досить абстрактний.
ЖакБ

@JacquesB Я намагаюся уникати проблем із реляційним опором об'єктів із надійною моделлю доменів, але також абстрактним, що відходить від моїх моделей перегляду в реалізації mvc.
розробник

Рід Cospey має багато позитивних речей , щоб сказати про IQuerable тут: stackoverflow.com/questions/1578778/using-iqueryable-with-linq Це означає , що краще на транспортному рівні. Що стосується абстракції, я знайшов користь для роботи загальної структури репозиторію, коли мені потрібно було ввести EntityType, але все ж хотів підтримувати загальні методи. З іншого боку, я сам стверджував на форумі MSDN LINQ, що EF - це сховище, оскільки це все в пам'яті. В одному проекті були використані численні пропозиції Where як виклики методів, які добре працювали.
Джон Петерс

Але одна з питань, яку я розглядав, але відкидав, - це бізнес із дерева виразів, деяким він дуже подобається, але .... вам доведеться це важко вивчити, перш ніж ви вважаєте його корисним.
Джон Петерс

2
нам слід повернутися до виклику SQL від контролерів
bobek

Відповіді:


12

Я думаю, ви поєднуєте сховища та загальні сховища.

Основний сховище просто взаємодіє з вашим сховищем даних і надає методи повернення даних

IRepository {
   List<Data> GetDataById(string id);
}

Він не просочує рівень даних у ваш код за допомогою IQueryable або інших способів передачі випадкових запитів і забезпечує чітко визначену перевірку та ін'єкційну поверхню методів.

Загальний репозиторій дозволяє передавати ваш запит так само, як ORM

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
    //or
    IQueryable<T> Get<T>();
}

Я згоден, немає великого сенсу використовувати загальний репозиторій поверх ORM, який є лише іншим загальним сховищем.

Відповідь полягає у використанні основного шаблону репозиторію, щоб приховати ORM


1
ОРМ ОРМ? Я намагався бути смішним. Чому вам потрібно абстрагувати ORM?
johnny

1
З тієї ж причини ви абстрактно будь-що. щоб уникнути забруднення вашого коду власними класами
Еван

6

Більшість аргументів, які ви згадуєте, неправильно відносять до особливостей шаблону репозиторію, яких він не має.

Концептуально сховище, як це було визначено в DDD, - це лише сукупність об'єктів, до яких можна шукати або додавати. Механізм стійкості, який стоїть за ним, абстрагується, тому, як споживач, ви отримуєте ілюзію, що це колекція пам'яті.

Реалізація сховища, яка має витікаючі абстракції (викриття, IQueryablesнаприклад), є поганою реалізацією сховища.

Реалізація сховища, яка відкриває більше, ніж просто операції збору (наприклад, функції Unit Work), є поганою реалізацією сховища.

Чи є альтернативи сховищу для доступу до даних? Так, але вони не пов'язані з проблемами, які ви ставите перед своїм питанням.



1

Для мене сховища в поєднанні з ORM або іншими стійкими шарами БД мають такі недоліки:

  1. Приховування одиниць праці. UoW повинен бути закодований програмістом і його рідко можна реалізувати як якусь магію на задньому плані, коли користувач просто робить запити та модифікації, не визначаючи меж UoW та, можливо, точки фіксації. Іноді від UoW відмовляються, зменшуючи їх у мікро UoW (наприклад, сеанси NHibernate) у кожному методі доступу до репозиторію.
  2. Приховування, або, в гіршому випадку, знищення стійкості ігнорування: Методи, такі як "Завантажити ()", "Отримати ()", "Зберегти ()" або "Оновити ()", пропонують негайні операції з одним об'єктом, як би надсилаючи окремих SQL / DML або як би працюючи з файлами. Насправді, наприклад, методи NHibernate з цими оманливими іменами, як правило, не роблять індивідуального доступу, а вимагають ледачого завантаження або вставки / оновлення партії (Persistent Ignorance). Іноді програмісти задаються питанням, чому вони не отримують негайних операцій з БД і насильно руйнують стійкість ігнорування, тим самим вбиваючи продуктивність і використовуючи основні зусилля, щоб фактично погіршити систему.
  3. Безконтрольний ріст. Простий сховище може накопичувати все більше і більше методів, щоб відповідати конкретним потребам.

Як от:

public interface ICarsRepository  /* initial */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); // bad, should be for multiple IDs.
    void SaveCar(ICar carToSave); // bad, no individual saves, use UoW commit!
}

public interface ICarsRepository  /* a few years later */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); 
    IList<ICar> GetBlueCars();
    IList<ICar> GetRedYellowGreenCars();
    IList<ICar> GetCarsByColor(Color colorOfCars); // a bit better
    IList<ICar> GetCarsByColor(IEnumerable<Color> colorsOfCars); // better!
    IList<ICar> GetCarsWithPowerBetween(int hpFrom, int hpTo);
    IList<ICar> GetCarsWithPowerKwBetween(int kwFrom, int kwTo);
    IList<ICar> GetCarsBuiltBetween(int yearFrom, int yearTo);
    IList<ICar> GetCarsBuiltBetween(DateTime from, DateTime to); // some also need month and day
    IList<ICar> GetHybridCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetElectricCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetCarsFromManufacturer(IManufacturer carManufacturer); 
    bool HasCarMeanwhileBeenChangedBySomebodyElseInDb(ICar car); // persistence ignorance broken
    void SaveCar(ICar carToSave);
}

4. Небезпека Бога: заперечення: вам може сподобатися створити один клас богів, що охоплюватиме всі ваші моделі або рівень доступу до даних. Клас сховища міститиме не лише методи Car, але й методи для всіх сутностей.

На мою думку, краще запропонувати хоча б деякі можливості запиту, щоб уникнути величезного безладу багатьох методів єдиної мети. Незалежно від того, чи це LINQ, власна мова запитів або навіть щось, взяте безпосередньо з ORM (ОК, якась проблема з’єднання ...).


4
Куди йдуть усі ці методи, якщо не використовується шаблон сховища?
Johnny


1

Якщо мета Repository-інтерфейс знущатися геть базу даних для UnitTest (= тест в ізоляції) найкращою абстракції є те , що легко насміхатися.

Складно знущатися з інтерфейсу сховища, який базується на результаті IQueryable.

З точки зору одиничного тестування

IRepository {
   List<Data> GetDataById(string id);
}

можна знущатися легко

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
}

можна знущатися легко, лише якщо макет ігнорує вміст параметра запиту.

IGenericRepository<T> {
    IQueryable<T> Get<T>(some_parameters);
}

не можна легко знущатися


0

Я не думаю, що шаблон сховища є надмірним, якщо ви використовуєте лямбда-функції для запитів. Тим більше, коли вам доведеться абстрагувати ORM (на мою думку, це завжди слід), я не буду дбати про деталі реалізації самого репозиторію.

Наприклад:

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        UserRepository ur = new UserRepository();
        var userWithA = ur.GetBy(u => u.Name.StartsWith("A"));

        Console.WriteLine(userWithA.Name);


        ur.GetAllBy(u => u.Name.StartsWith("M"))
          .ForEach(u => Console.WriteLine(u.Name));


        ur.GetAllBy(u => u.Age > 13)
          .ForEach(u => Console.WriteLine(u.Name));
    }
}

public class UserRepository 
{
    List<User> users = new List<User> { 
        new User{Name="Joe", Age=10},
            new User{Name="Allen", Age=12},
            new User{Name="Martin", Age=14},
            new User{Name="Mary", Age=15},
            new User{Name="Ashton", Age=29}
    };

    public User GetBy(Predicate<User> userPredicate)
    {
        return users.Find(userPredicate);
    }

    public List<User> GetAllBy(Predicate<User> userPredicate)
    {
        return users.FindAll(userPredicate);
    }
}

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.