НЕ використовуючи шаблон сховища, використовуйте ORM як є (EF)


95

Я завжди використовував шаблон сховища, але для свого останнього проекту я хотів побачити, чи зможу я вдосконалити його використання та свою реалізацію “Unit Of Work”. Що більше я починав копати, то став задавати собі питання: "А мені це справді потрібно?"

Тепер все починається з декількох коментарів до Stackoverflow із слідом до допису Айенде Рахієн у його блозі, з 2 конкретними,

Про це, мабуть, можна говорити вічно і назавжди, і це залежить від різних додатків. Що я люблю знати,

  1. чи підходив би такий підхід для проекту Entity Framework?
  2. використовуючи цей підхід, бізнес-логіка все ще працює на рівні обслуговування, або методи розширення (як пояснено нижче, я знаю, метод розширення використовує сесію NHib)?

Це легко зробити за допомогою методів розширення. Чистий, простий і багаторазовий.

public static IEnumerable GetAll(
    this ISession instance, Expression<Func<T, bool>> where) where T : class
{
    return instance.QueryOver().Where(where).List();
}

Використовуючи цей підхід та Ninjectяк DI, чи потрібно мені робити Contextінтерфейс та вводити його в мої контролери?

Відповіді:


103

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

Кодування для виключення

Чи кодуєте ви на 1% шансів, що ваша база даних зміниться з однієї технології на іншу? Якщо ви думаєте про майбутній стан свого бізнесу і відповідаєте "так", це можливо, тоді а) вони повинні мати багато грошей, щоб дозволити собі перехід на іншу технологію БД, або б) ви вибираєте технологію БД для розваги або с ) щось сталося жахливо не так із першою технологією, яку ви вирішили використовувати.

Навіщо викидати багатий синтаксис LINQ?

LINQ та EF були розроблені, щоб ви могли робити з ними акуратні речі для читання та обходу графіків об'єктів. Створення та підтримка сховища, яке може надати вам таку ж гнучкість для цього, є жахливим завданням. З мого досвіду, коли б я не створював сховище, Я ЗАВЖДИ мав витік бізнес-логіки на рівень сховища, щоб або зробити запити більш ефективними, і / або зменшити кількість звернень до бази даних.

Я не хочу створювати метод для кожної окремої перестановки запиту, який я повинен написати. Я міг би також написати збережені процедури. Я не хочуGetOrder , GetOrderWithOrderItem, GetOrderWithOrderItemWithOrderActivity, GetOrderByUserId, і так далі ... Я просто хочу , щоб отримати основну сутність і траверс і включають в себе граф об'єктів , як я так будь ласка.

Більшість прикладів сховищ - це фігня

Якщо ви не розробляєте щось ДЕЙСТВИТЕЛЬНО оголене, як блог чи щось, ваші запити ніколи не будуть такими простими, як 90% прикладів, які ви знайдете в Інтернеті навколо шаблону сховища. Я не можу наголосити на цьому досить! Це те, що потрібно пролізти крізь грязь, щоб зрозуміти. Завжди знайдеться той один запит, який порушує ваше досконало продумане сховище / рішення, яке ви створили, і лише до того моменту, коли ви вдруге самі здогадаєтесь, і починається технічний борг / ерозія.

Не перевіряйте мене, брате

Але як щодо модульного тестування, якщо у мене немає сховища? Як я буду знущатися? Просто, ти ні. Давайте розглянемо це з обох сторін:

Немає сховища - Ви можете знущатись над DbContextвикористанням IDbContextдеяких чи інших прийомів, але тоді ви справді модульно тестуєте LINQ для об’єктів, а не LINQ для сутностей оскільки запит визначається під час виконання ... Добре, це не добре! Отож, зараз це залежить від тесту інтеграції.

За допомогою сховища - тепер ви можете знущатися над своїми сховищами та модульно тестувати шари між ними. Чудово, правда? Ну не дуже ... У наведених вище випадках, коли вам потрібно просочити логіку на рівень сховища, щоб зробити запити більш ефективними та / або менше звернень до бази даних, як ваші модульні тести можуть це покрити? Зараз це в репо-шарі, і ви не хочете тестувати, IQueryable<T>чи не так? Також будьмо чесними, ваші модульні тести не будуть охоплювати запити, які містять .Where()речення із 20 рядків та.Include()є купа взаємозв’язків і знову потрапляє в базу даних, щоб робити всі ці інші речі, бла-бла-бла, так чи інакше, оскільки запит генерується під час виконання. Крім того, оскільки ви створили сховище для збереження стійкості верхніх шарів у несвідомості, якщо ви зараз хочете змінити технологію бази даних, вибачте, що ваші модульні тести точно не гарантуватимуть однакових результатів під час виконання, повернувшись до тестів інтеграції. Тож вся суть сховища здається дивною ..

2 центи

Ми вже втрачаємо багато функціональних можливостей та синтаксису, використовуючи EF над звичайними збереженими процедурами (масові вставки, масове видалення, CTE тощо), але я також кодую на C #, тому мені не потрібно вводити двійковий файл. Ми використовуємо EF, щоб мати можливість використовувати різних провайдерів та працювати з графіками об’єктів у приємній взаємопов’язаній формі серед багатьох речей. Певні абстракції корисні, а деякі ні.


17
Ви не створюєте сховищ, щоб мати можливість їх модульного тестування. Ви створюєте сховища, щоб мати можливість модульного тестування бізнес-логіки . Що стосується того, щоб переконатися, що запити працюють: набагато простіше написати інтеграційні тести для сховищ, оскільки вони містять лише логіку, а не будь-який бізнес.
jgauffin

16
Coding for the exception: Використання репозиторіїв не означає перемикання механізму баз даних. Йдеться про відокремлення бізнесу від наполегливості.
jgauffin

2
Це все дуже вагомі моменти, за якими стоїть велика кількість правди. Однак не вистачає усвідомлення того, що LINQ, розкиданий про додаток, а не обмежений послідовним розташуванням, створює EF-еквівалент викликів SQL на кодових сторінках. Кожен запит LINQ є потенційною точкою обслуговування в додатку, і чим їх більше (і чим вони поширеніші), тим вищі витрати на обслуговування та ризики. Уявіть, що ви додаєте прапорець "видалено" до сутності та мусите знаходити кожне окреме місце у великому додатку, до якого запитується сутність, маючи змінити кожну ...
DVK

2
Я думаю, що це недалекоглядно і змучене. Чому б вам просочуватися логіка в репо? А якби ви це зробили, чому це мало значення? Це реалізація даних. Все, що ми робимо, - це відгороджувати LINQ від решти коду, приховуючи його за репо. Ви говорите, що не перевіряйте, але потім використовуєте неможливість перевірити це як аргумент проти цього. Тож робіть репо, не виставляйте IQueryable і не тестуйте його. Принаймні ви можете протестувати все інше ізольовано від реалізації даних. І що 1% шансів на зміну рівня дБ все ще є величезним $ -розі.
Sinaesthetic

5
+1 за цю відповідь. Я виявляю, що нам дійсно НЕ потрібні сховища з Entity Framework Core. DbSetЄ сховищем , і DbContextє одиницею роботи . Навіщо реалізовувати шаблон репозиторію, коли ORM це вже робить за нас! Для тестування просто змініть постачальника на InMemory. І зробіть свої тести! Це добре задокументовано в MSDN.
Мохаммед Нурелдін

49

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

Проблема полягає в тому, що багато розробників не розуміють призначення шаблонів і створюють сховища, які передають певну стійкість до інформації, що викликає (зазвичай шляхом викриття IQueryable<T>). Роблячи це, вони не отримують вигоди від безпосереднього використання АБО / М.

Оновіть, щоб звернутися до іншої відповіді

Кодування для виключення

Використання сховищ не стосується можливості переключити технологію постійності (тобто змінити базу даних або замість цього скористатися веб-службою тощо). Йдеться про відокремлення бізнес-логіки від наполегливості для зменшення складності та взаємозв’язку.

Юніт-тести проти інтеграційних тестів

Ви не пишете модульні тести для сховищ. період.

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

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

Різниця полягає в тому, що якщо ви не змішали свій бізнес із виписками LINQ, ви можете бути на 100% впевнені, що не вдається ваш код постійності, а не щось інше.

Якщо ви проаналізуєте свої тести, ви також побачите, що вони набагато чистіші, якщо у вас немає суперечливих проблем (тобто LINQ + бізнес-логіка)

Приклади сховищ

Більшість прикладів - фігня. це дуже правда. Однак, якщо ви погуглите будь-який шаблон дизайну, ви знайдете безліч придурків. Це не причина уникати використання шаблону.

Створити правильну реалізацію сховища дуже просто. Насправді вам потрібно дотримуватися єдиного правила:

Не додайте нічого до класу сховища до самого моменту, коли вам це потрібно

Багато кодерів ледачі і намагається створити загальне сховище та використовувати базовий клас з безліччю методів, які їм можуть знадобитися. ЯГНІ. Клас сховища ви пишете один раз і зберігаєте його до тих пір, поки програма діє (може бути роками). Навіщо трахати це, лінуючись. Зберігайте чистоту без успадкування базового класу. Це значно полегшить читання та обслуговування.

.

Старі речі

Висновок:

Якщо ви не проти того, щоб у вашому коді бізнесу були виписки LINQ, а також про огляд модульних тестів, я не бачу причин не використовувати безпосередньо Entity Framework.

Оновлення

Я писав блоги як про шаблон сховища, так і про те, що насправді означає "абстракція": http://blog.gauffin.org/2013/01/repository-pattern-done-right/

Оновлення 2

Як ви розробите метод запиту для підтримки будь-якої комбінації перестановок для одиничного типу із 20 і більше полів? Ви не хочете обмежувати пошук лише за назвою, що стосується пошуку за властивостями навігації, перераховувати всі замовлення з товаром із зазначенням певного коду ціни, 3 рівень пошуку властивостей навігації. Причиною всього IQueryableбуло винайдено можливість скласти будь-яку комбінацію пошуку за базою даних. У теорії все виглядає чудово, але потреби користувача перемагають вище теорії.

Знову ж таки: Суб'єкт із 20+ полями неправильно змодельований. Це сутність БОГА. Розбити його.

Я не сперечаюся, що IQueryableце не для створення запиту. Я кажу, що це неправильно для абстракційного шару, такого як шаблон сховища, оскільки він негерметичний. Немає 100% повного постачальника LINQ To Sql (як EF).

Всі вони мають специфічні для реалізації речі, наприклад, як використовувати охоче / ледаче завантаження або як робити оператори SQL "IN". Викриття IQueryableв сховищі змушує користувача знати всі ці речі. Таким чином, вся спроба абстрагуватися від джерела даних є повною невдачею. Ви просто додаєте складності, не отримуючи жодної вигоди від безпосереднього використання OR / M.

Або впровадьте шаблон Repository правильно, або просто не використовуйте його взагалі.

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


6
Відсутність IQueryable призводить до обмеженого пошуку, і в підсумку люди створюють більше методів Get для різних типів запитів, і врешті-решт це робить сховище більш складним.
Akash Kava

3
Ви взагалі не розглядали основну проблему: Викриття IQueryable через сховище не є повною абстракцією.
jgauffin

1
Наявність об’єкта запиту, який містить усю необхідну інфраструктуру, яка сама по собі буде виконана, - це шлях до імо. Ви надаєте йому поля, які є пошуковими термінами, і він повертає список результатів. Внутрішній контроль якості ви можете робити все, що завгодно. І це інтерфейс, який так легко перевірити. Дивіться мій пост вище. Це найкраще.
h.alex

2
Особисто я вважаю, що також має сенс реалізувати інтерфейс IQueryable <T> в класі сховища, а не виставляти базовий набір в одному з його членів.
dark_perfect 02

3
@yat: Один репозиторій на сукупний корінь. Але це не сукупний корінь та сукупність таблиць, а просто сукупний корінь та сукупності . Фактичне сховище може використовувати лише одну таблицю або багато з них, тобто це може бути не одне ціле відображення між кожним агрегатом та таблицею. Я використовую сховища, щоб зменшити складність і видалити будь-які залежності базового сховища.
jgauffin

27

ІМО, і Repositoryабстракція, і UnitOfWorkабстракція займають дуже цінне місце в будь-якому значущому розвитку. Люди будуть сперечатися щодо деталей реалізації, але так само, як існує безліч способів зняти шкіру з кота, існує багато способів здійснити абстракцію.

Ваше питання конкретно використовувати чи не використовувати і чому.

Оскільки ви, без сумніву, зрозуміли, що ці два шаблони вже вбудовані в Entity Framework, DbContextє UnitOfWorkі DbSetє Repository. Як правило, вам не потрібно юніт-тестувати себе UnitOfWorkабо Repositoryсебе, оскільки вони просто сприяють між вашими класами та базовими реалізаціями доступу до даних. Що вам доведеться робити знову і знову, це глузування з цих двох абстракцій під час модульного тестування логіки ваших послуг.

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

Отже, другорядним моментом є те , що наявність власної абстракції UnitOfWorkта Repositoryдає вам максимальний контроль та гнучкість при знущанні над вашими модульними тестами.

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

Отже, у вас є IRepository:

public interface IRepository<T>
    where T : class
{
    T Add(T entity);
    void Delete(T entity);
    IQueryable<T> AsQueryable();
}

І його реалізація:

public class Repository<T> : IRepository<T>
    where T : class
{
    private readonly IDbSet<T> _dbSet;
    public Repository(PPContext context) 
    {
        _dbSet = context.Set<T>();
    }

    public T Add(T entity)
    { 
        return _dbSet.Add(entity); 
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity); 
    }

    public IQueryable<T> AsQueryable() 
    {
        return _dbSet.AsQueryable();
    }
}

Поки що нічого незвичайного, але зараз ми хочемо додати кілька протоколів - легко з реєстрацією Декоратора .

public class RepositoryLoggerDecorator<T> : IRepository<T>
    where T : class
{
    Logger logger = LogManager.GetCurrentClassLogger();
    private readonly IRepository<T> _decorated;
    public RepositoryLoggerDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
        T added = _decorated.Add(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        return added;
    }

    public void Delete(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        _decorated.Delete(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable();
    }
}

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

Зараз я багато разів бачив це запитання на StackOverflow - "як зробити так, щоб Entity Framework працював у середовищі з декількома орендарями?".

https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant

Якщо у вас Repositoryабстракція, тоді відповідь: "легко додати декоратора"

public class RepositoryTennantFilterDecorator<T> : IRepository<T>
    where T : class
{
    //public for Unit Test example
    public readonly IRepository<T> _decorated;
    public RepositoryTennantFilterDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        return _decorated.Add(entity);
    }

    public void Delete(T entity)
    {
        _decorated.Delete(entity);
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable().Where(o => true);
    }
}

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

Відповідь, яка зазвичай спадає на думку, коли хтось каже: «навіщо мені абстракція (наприклад, Repository ) над тією чи іншою бібліотекою третьої сторони", - "чому б вам не?"

PS Decorators надзвичайно просто застосовувати за допомогою контейнера IoC, такого як SimpleInjector .

[TestFixture]
public class IRepositoryTesting
{
    [Test]
    public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
    {
        Container container = new Container();
        container.RegisterLifetimeScope<PPContext>();
        container.RegisterOpenGeneric(
            typeof(IRepository<>), 
            typeof(Repository<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryLoggerDecorator<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryTennantFilterDecorator<>));
        container.Verify();

        using (container.BeginLifetimeScope())
        {
            var result = container.GetInstance<IRepository<Image>>();

            Assert.That(
                result, 
                Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
            Assert.That(
                (result as RepositoryTennantFilterDecorator<Image>)._decorated,
                Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
        }
    }
}

11

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

Макетоване сховище для модульних тестів, нам воно справді потрібне?

Ми дозволяємо EF спілкуватися для тестування БД в модульних тестах, щоб перевірити нашу бізнес-логіку прямо проти БД тестування SQL. Я не бачу жодної вигоди від насмішок над будь-яким шаблоном сховища взагалі. Що насправді неправильно, виконуючи модульні тести проти бази тестів? Оскільки масові операції неможливі, і в підсумку ми пишемо сирий SQL. SQLite в пам'яті є ідеальним кандидатом для проведення модульних тестів проти реальної бази даних.

Непотрібна абстракція

Ви хочете створити сховище лише для того, щоб у майбутньому ви могли легко замінити EF на NHbibernate тощо або щось інше? Звучить чудовий план, але чи справді це рентабельно?

Linq вбиває одиничні тести?

Я хотів би побачити будь-які приклади того, як це може вбити.

Інжекція залежності, IoC

Ого, це чудові слова, звичайно, вони виглядають чудово в теорії, але іноді доводиться вибирати компроміс між чудовим дизайном та чудовим рішенням. Ми все це використали, і в підсумку викинули все на смітник і обрали інший підхід. Розмір проти швидкості (розмір коду та швидкість розробки) має величезне значення в реальному житті. Користувачам потрібна гнучкість, їм байдуже, чи ваш код чудовий у дизайні з точки зору DI або IoC.

Якщо ви не будуєте Visual Studio

Всі ці чудові конструкції потрібні, якщо ви створюєте складну програму, таку як Visual Studio або Eclipse, яку розроблятиме багато людей, і вона повинна бути дуже настроюваною. Всі чудові моделі розвитку з’явилися після багатьох років розвитку цих середовищ розробки, і вони еволюціонували там, де всі ці чудові моделі дизайну так важливі. Але якщо ви робите простий веб-фонд заробітної плати або простий бізнес-додаток, краще, щоб ви еволюціонували у своїй розробці з часом, замість того, щоб витрачати час на його створення для мільйонів користувачів, де він буде розгорнутий лише для сотих користувачів.

Сховище як відфільтрований вигляд - ISecureRepository

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

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

Наступна відповідь - приклад реалізації відфільтрованого сховища без створення цілого набору класів і методів. Він може не відповісти безпосередньо на запитання, але може бути корисним при його виведенні.

Застереження: Я автор Entity REST SDK.

http://entityrestsdk.codeplex.com

Маючи на увазі вище, ми розробили SDK, який створює сховище відфільтрованого подання на основі SecurityContext, яке містить фільтри для CRUD-операцій. І лише два типи правил спрощують будь-які складні операції. Перший - це доступ до сутності, а інший - правило читання / запису для властивості.

Перевага полягає в тому, що ви не переписуєте бізнес-логіку або сховища для різних типів користувачів, ви просто блокуєте або надаєте їм доступ.

public class DefaultSecurityContext : BaseSecurityContext {

  public static DefaultSecurityContext Instance = new DefaultSecurityContext();

  // UserID for currently logged in User
  public static long UserID{
       get{
             return long.Parse( HttpContext.Current.User.Identity.Name );
       }
  }

  public DefaultSecurityContext(){
  }

  protected override void OnCreate(){

        // User can access his own Account only
        var acc = CreateRules<Account>();

        acc.SetRead( y => x=> x.AccountID == UserID ) ;
        acc.SetWrite( y => x=> x.AccountID == UserID );

        // User can only modify AccountName and EmailAddress fields
        acc.SetProperties( SecurityRules.ReadWrite, 
              x => x.AccountName,
              x => x.EmailAddress);

        // User can read AccountType field
        acc.SetProperties<Account>( SecurityRules.Read, 
              x => x.AccountType);

        // User can access his own Orders only
        var order = CreateRules<Order>();
        order.SetRead( y => x => x.CustomerID == UserID );

        // User can modify Order only if OrderStatus is not complete
        order.SetWrite( y => x => x.CustomerID == UserID 
            && x.OrderStatus != "Complete" );

        // User can only modify OrderNotes and OrderStatus
        order.SetProperties( SecurityRules.ReadWrite, 
              x => x.OrderNotes,
              x => x.OrderStatus );

        // User can not delete orders
        order.SetDelete(order.NotSupportedRule);
  }
}

Ці Правила LINQ обчислюються з Базою даних у методі SaveChanges для кожної операції, і ці Правила діють як Брандмауер перед Базою даних.


3
Модульне тестування на базі даних означає, що у вас є додаткові вимоги до своїх тестів. Якщо ця БД не працює, або дані очищаються, або з цією БД щось трапляється, Ваші тести не зможуть. Це не бажано. Сховища, які виставляють IQueryable, займають близько 2 хвилин для налаштування. Тут часу не витрачали даремно. Чому DI зайняв у вас багато часу? Все це займає хвилини. Скажу, все це чудово працювало для модульного тестування моїх складних запитів на моєму сервісному рівні. Це було так приємно не потребувати бази даних для підключення. Отримання глузливого фреймворку від nuget зайняло близько хвилини. Ці речі не займають часу.
user441521

@ user441521 Репозиторії з IQueryable 2 хв для налаштування? в якому світі ви живете, кожен запит asp.net на нашому веб-сайті подається протягом мілісекунд. Знущання, підробка тощо додає коду ще більшої складності, це загальна втрата часу. Блокові тести марні, коли одиниця не визначена як одиниця бізнес-логіки.
Akash Kava

7

Існує багато суперечок щодо того, який метод є правильним, тому я дивлюся на нього, оскільки обидва є прийнятними, тому я коли-небудь використовую той, який мені подобається найбільше (Який не є сховищем, UoW).

В EF UoW реалізовано через DbContext, а набори даних - це сховища.

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

Я вважаю, що Ayende також має кілька публікацій про те, наскільки абстрагування операцій CUD є поганим.

Я завжди роблю інтерфейс і його контекст успадковую від нього, щоб я міг використовувати контейнер IoC для DI.


Тож методи розширення, наскільки вони широкі? Скажімо, мені потрібно отримати стан іншої сутності у своєму розширенні? Зараз це моє найбільше занепокоєння. Ви не проти показати кілька прикладів методів розширення?
Dejan.S

ayende.com/blog/153473/… та ayende.com/blog/153569/… . (Це огляди архітектури (Framework?), Яка називається s # arp lite. В основному хороша, але він не погоджується зі сховищами та абстракціями CUD).
Джош

На основі NHibernate. Хіба у вас немає прикладів використання EF? І знову, коли мені потрібно зателефонувати іншій сутності, як це найкраще підходить для методу статичного розширення?
Dejan.S 02

3
Це все добре, поки властивість об’єкта вашого домену не потребує зволоження даними, які не зберігаються у вашій базі даних; або вам потрібно скористатися технологією, яка є більш ефективною, ніж ваша роздута ORM. OOPS! ORM - це просто НЕ заміна сховища, це деталь реалізації одного.
cdaq

2

Що найбільше стосується EF, це не шаблон сховища. Це шаблон фасаду (абстрагування викликів методів EF у простіші та простіші у використанні версії).

EF - це той, який застосовує шаблон сховища (і шаблон одиниці роботи також). Тобто EF - це той, що абстрагує рівень доступу до даних, так що користувач не уявляє, що має справу з SQLServer.

І при цьому, більшість "сховищ" над EF навіть не є хорошими фасадами, оскільки вони просто, цілком прямолінійно, відображають окремі методи в EF, навіть до того, що мають однакові підписи.

Отже, дві причини застосування цього так званого шаблону "сховища" над EF полягають у спрощенні тестування та встановленні підмножини "консервованих" викликів до нього. Самі по собі непогані, але явно не сховище.


1

Linq - це сьогодні «сховище».

ISession + Linq вже є сховищем, і вам не потрібні ні GetXByYметоди, ні QueryData(Query q)узагальнення. Будучи трохи параноїком щодо використання DAL, я все ще віддаю перевагу інтерфейсу сховища. (З точки зору ремонтопридатності ми також все одно повинні мати певний фасад над певними інтерфейсами доступу до даних).

Ось сховище, яке ми використовуємо - воно відключає нас від прямого використання nhibernate, але надає інтерфейс linq (як доступ до ISession у виняткових випадках, які в кінцевому підсумку підлягають рефактору).

class Repo
{
    ISession _session; //via ioc
    IQueryable<T> Query()
    {
        return _session.Query<T>();
    }
}

Що ви робите для сервісного рівня?
Dejan.S 02

Контролери запитують репо для даних лише для читання, навіщо додавати якийсь додатковий шар? Інша можливість полягає у використанні "ContentService", яке все більше і більше має тенденцію бути сховищем рівня послуг: GetXByY тощо тощо. Для операцій модифікації - служби додатків - це просто абстракції над випадками використання - вони вільно використовують BL і репо
mikalai

Я звик робити сервісний рівень для бізнес-логіки. Я не впевнений, що слідую за Вами за допомогою ContentService. Чи буде поганою практикою робити допоміжні класи як "сервісний рівень"?
Dejan.S 02

Під "сервісним рівнем" я мав на увазі "служби додатків". Вони можуть використовувати сховище та будь-яку іншу загальнодоступну частину рівня домену. "Сервісний рівень" - це не погана практика, але я б уникав створення класу XService просто для надання результату List <X>. Поле коментаря здається занадто коротким, щоб детально описати послуги, вибачте.
mikalai

Що робити, якщо, скажімо, розрахунок кошика, і вам потрібно отримати параметри налаштувань програми та конкретні параметри замовника, щоб зробити розрахунок, і це повторно використовується в декількох місцях програми. Як ви вирішуєте цю ситуацію? допоміжний клас або служба додатків?
Dejan.S

1

Зараз сховище (або, як би хтось не вирішив його викликати) для мене в основному стосується абстрагування шару стійкості.

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

Отже, я, як правило, маю

public interface IRepository : IDisposable
{
    void Save<TEntity>(TEntity entity);
    void SaveList<TEntity>(IEnumerable<TEntity> entities);

    void Delete<TEntity>(TEntity entity);
    void DeleteList<TEntity>(IEnumerable<TEntity> entities);

    IList<TEntity> GetAll<TEntity>() where TEntity : class;
    int GetCount<TEntity>() where TEntity : class;

    void StartConversation();
    void EndConversation();

    //if query objects can be self sustaining (i.e. not need additional configuration - think session), there is no need to include this method in the repository.
    TResult ExecuteQuery<TResult>(IQueryObject<TResult> query);
}

Можливо, додайте асинхронні методи із зворотними викликами в якості делегатів. РЕПО легко впровадити загалом , тому я не можу торкатися рядка реалізації від програми до програми. Ну, це правда принаймні при використанні NH, я робив це також з EF, але змусив ненавидіти EF. 4. Розмова - це початок транзакції. Дуже круто, якщо кілька класів спільно використовують екземпляр сховища. Крім того, для NH одне репо в моїй реалізації дорівнює одній сесії, яка відкривається за першим запитом.

Потім об’єкти запиту

public interface IQueryObject<TResult>
{
    /// <summary>Provides configuration options.</summary>
    /// <remarks>
    /// If the query object is used through a repository this method might or might not be called depending on the particular implementation of a repository.
    /// If not used through a repository, it can be useful as a configuration option.
    /// </remarks>
    void Configure(object parameter);

    /// <summary>Implementation of the query.</summary>
    TResult GetResult();
}

Для конфігурації я використовую в NH лише для передачі ISession. У EF немає сенсу більш-менш.

Прикладом запиту може бути .. (NH)

public class GetAll<TEntity> : AbstractQueryObject<IList<TEntity>>
    where TEntity : class
{
    public override IList<TEntity> GetResult()
    {
        return this.Session.CreateCriteria<TEntity>().List<TEntity>();
    }
}

Щоб зробити запит EF, ви повинні мати контекст у базі Abstract, а не сеанс. Але, звичайно, ifc був би тим самим.

Таким чином, запити самі інкапсулюються та легко перевіряються. Найкраще, мій код покладається лише на інтерфейси. Все дуже чисто. Об’єкти домену (бізнесу) - це саме те, наприклад, немає змішування відповідальності, як при використанні шаблону активного запису, який навряд чи піддається тестуванню та змішує код доступу (запиту) до даних в об’єкті домену, і при цьому відбувається змішування проблем (об’єкт, який отримує сам ??). Усі ще можуть вільно створювати POCO для передачі даних.

Загалом, такий підхід забезпечує багато повторного використання коду та простоту, втрачаючи не те, що я можу собі уявити. Будь-які ідеї?

І велике спасибі Айенду за його чудові посади та продовження відданості. Його ідеї тут (об'єкт запиту), а не мої.


1
Суб’єкти стійкості (ваші POCO) НЕ є суб’єктами господарювання / домену. А мета сховища - відокремити бізнес (або будь-який) рівень від стійкості.
MikeSW

Я не бачу зчеплення. Дещо згодні з частиною POCO, але все одно. Ніщо не заважає вам мати «справжні» POCO і все ще використовувати цей підхід.
h.alex

1
Суб'єкти взагалі не повинні бути німими POCO. Насправді моделювання ділової логіки в Entities - це те, що постійно робить натовп DDD. Цей стиль розробки дуже добре поєднується з NH або EF.
chris

1

Для мене це просте рішення з відносно невеликою кількістю факторів. Факторами є:

  1. Репозиторії призначені для доменних класів.
  2. В деяких моїх програмах класи доменів такі самі, як класи моєї стійкості (DAL), в інших - ні.
  3. Коли вони однакові, EF вже надає мені сховища.
  4. EF забезпечує ледаче завантаження та IQueryable. Мені подобаються ці.
  5. Абстрагування / «фасадне» / повторне впровадження сховища над EF зазвичай означає втрату ледачого та IQueryable

Отже, якщо мій додаток не може виправдати №2, розділити моделі домену та даних, то я зазвичай не буду турбуватись No5.

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