НЕ используя шаблон репозитория, используйте ORM как есть (EF)

96

Я всегда использовал шаблон репозитория, но в своем последнем проекте я хотел посмотреть, смогу ли я усовершенствовать его использование и мою реализацию «Unit Of Work». Чем больше я начал копать, тем больше я задавался вопросом: «А мне это действительно нужно?».

Теперь все начинается с пары комментариев к Stackoverflow со следом к сообщению Айенде Рахиен в его блоге, с двумя конкретными,

Об этом, вероятно, можно было бы говорить вечно, и это зависит от разных приложений. Что мне нравится знать,

  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интерфейс и внедрять его в свои контроллеры?

Dejan.S
источник

Ответы:

104

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

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

Вы кодируете с вероятностью 1%, что ваша база данных изменится с одной технологии на другую? Если вы думаете о будущем состоянии вашего бизнеса и говорите, что да, это возможно, тогда а) у них должно быть много денег, чтобы позволить себе переход на другую технологию БД, или б) вы выбираете технологию БД для развлечения, или ) что-то пошло не так с первой технологией, которую вы решили использовать.

Зачем отказываться от богатого синтаксиса LINQ?

LINQ и EF были разработаны, чтобы с ними можно было делать изящные вещи для чтения и просмотра графов объектов. Создание и поддержка репозитория, который может дать вам такую ​​же гибкость, - это чудовищная задача. По моему опыту, каждый раз, когда я создавал репозиторий, у меня ВСЕГДА была утечка бизнес-логики на уровень репозитория, чтобы либо сделать запросы более производительными, либо уменьшить количество обращений к базе данных.

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

Большинство примеров репозиториев - чушь собачья.

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

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

Но как насчет модульного тестирования, если у меня нет репозитория? Как я буду издеваться? Просто нет. Давайте посмотрим на это с двух сторон:

Нет репозитория - вы можете имитировать DbContextиспользование IDbContextили некоторые другие уловки, но тогда вы действительно тестируете LINQ to Objects, а не LINQ to Entities, потому что запрос определяется во время выполнения ... Хорошо, это не хорошо! Теперь дело за интеграционным тестом.

С репозиторием - теперь вы можете издеваться над своими репозиториями и проводить модульное тестирование промежуточных слоев. Отлично, правда? Ну, не совсем ... В приведенных выше случаях, когда вам нужно передать логику на уровень репозитория, чтобы сделать запросы более производительными и / или уменьшить количество обращений к базе данных, как ваши модульные тесты могут это покрыть? Теперь он находится на уровне репо, и вы не хотите тестировать, IQueryable<T>верно? Также давайте будем честны, ваши модульные тесты не будут охватывать запросы, содержащие .Where()предложение из 20 строк и.Include()это связка отношений и снова обращается к базе данных, чтобы сделать все эти другие вещи, бла, бла, бла в любом случае, потому что запрос генерируется во время выполнения. Кроме того, поскольку вы создали репозиторий для игнорирования персистентности верхних уровней, если теперь вы хотите изменить технологию своей базы данных, извините, что ваши модульные тесты определенно не гарантируют те же результаты во время выполнения, возвращаясь к интеграционным тестам. Так что весь смысл репозитория кажется странным ...

2 цента

Мы уже теряем много функциональных возможностей и синтаксиса при использовании EF вместо простых хранимых процедур (массовые вставки, массовые удаления, CTE и т. Д.), Но я также кодирую на C #, поэтому мне не нужно вводить двоичный код. Мы используем EF, поэтому у нас есть возможность использовать разных поставщиков и работать с графами объектов в приятной взаимосвязанной форме среди многих вещей. Некоторые абстракции полезны, а некоторые нет.

Райан
источник
18
Вы не создаете репозитории, чтобы иметь возможность их модульного тестирования. Вы создаете репозитории, чтобы иметь возможность модульного тестирования бизнес-логики . Что касается проверки работы запросов: гораздо проще написать интеграционные тесты для репозиториев, поскольку они содержат только логику, а не бизнес.
jgauffin
16
Coding for the exception: Использование репозиториев не позволяет переключать ядро ​​базы данных. Речь идет об отделении бизнеса от настойчивости.
jgauffin
2
Все это очень важные моменты, за которыми стоит большая доля правды. Однако не хватает осознания того, что LINQ, разбросанный по приложению, а не ограниченный согласованным расположением, создает EF-эквивалент вызовов SQL на страницах с выделенным кодом. Каждый запрос LINQ является потенциальной точкой обслуживания в приложении, и чем их больше (и чем они более распространены), тем выше затраты и риски на обслуживание. Представьте, что вы добавляете к объекту флаг «удален» и вам нужно найти каждое отдельное место в большом приложении, в котором запрашивается объект, и нужно изменить каждое ...
DVK,
2
Я считаю это недальновидным и пресыщенным. Зачем вы вводите логику в репо? А если да, то какое это имеет значение? Это реализация данных. Все, что мы делаем, - это отгораживаем LINQ от остальной части кода, пряча его за репозиторием. Вы говорите, что не проверяйте это, но затем используете невозможность проверить это как аргумент против этого. Так что сделайте репо, не выставляйте IQueryable и не тестируйте его. По крайней мере, вы можете протестировать все остальное отдельно от реализации данных. И этот 1% шанс изменения базы данных по-прежнему огромен.
Sinaesthetic 01
5
+1 за этот ответ. Я считаю, что нам действительно НЕ нужны репозитории с Entity Framework Core. DbSetЯвляется хранилищем , и DbContextявляется единицей работы . Зачем внедрять шаблон репозитория, если ORM уже делает это за нас! Для тестирования просто смените провайдера на InMemory. И сделай свои тесты! Это хорошо задокументировано в MSDN.
Мохаммед
49

Шаблон репозитория - это абстракция . Его цель - уменьшить сложность и сделать остальную часть кода неосведомленной. В качестве бонуса он позволяет писать модульные тесты вместо интеграционных .

Проблема в том, что многие разработчики не понимают назначение шаблонов и создают репозитории, которые передают информацию о сохраняемости вызывающей стороне (обычно путем раскрытия IQueryable<T>). Поступая так, они не получают выгоды от прямого использования OR / M.

Обновите, чтобы адресовать другой ответ

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

Использование репозиториев не означает возможность переключения технологии сохранения (например, изменение базы данных или использование веб-сервиса и т. Д.). Речь идет об отделении бизнес-логики от постоянства, чтобы уменьшить сложность и взаимосвязь.

Модульные тесты против интеграционных тестов

Вы не пишете модульные тесты для репозиториев. период.

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

Что касается запросов. Если вы используете LINQ, вы также должны убедиться, что ваши запросы работают, как и в случае с репозиториями. и это делается с помощью интеграционных тестов.

Разница в том, что если вы не смешали свой бизнес с операторами LINQ, вы можете быть на 100% уверены, что сбой происходит именно ваш код сохранения, а не что-то еще.

Если вы проанализируете свои тесты, вы также увидите, что они намного чище, если вы не смешиваете проблемы (например, LINQ + Business logic).

Примеры репозитория

Большинство примеров - чушь собачья. Это очень верно. Однако, если вы погуглите какой-либо шаблон дизайна, вы найдете множество дрянных примеров. Это не причина избегать использования шаблона.

Создать правильную реализацию репозитория очень просто. На самом деле вам нужно следовать только одному правилу:

Не добавляйте ничего в класс репозитория до того момента, когда вам это понадобится.

Многие программисты ленивы и пытаются создать общий репозиторий и использовать базовый класс с множеством методов, которые могут им понадобиться. ЯГНИ. Вы пишете класс репозитория один раз и храните его, пока живет приложение (может быть, годы). Зачем облажаться, будучи ленивым. Держите его в чистоте без наследования базового класса. Это упростит чтение и поддержку.

(Приведенное выше утверждение является руководством, а не законом. Базовый класс вполне может быть мотивирован. Просто подумайте, прежде чем добавлять его, чтобы добавлять его по правильным причинам)

Старье

Вывод:

Если вы не против использования операторов 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.

Либо правильно реализуйте шаблон репозитория, либо просто не используйте его.

(Если вы действительно хотите обрабатывать большие объекты, вы можете комбинировать шаблон репозитория с шаблоном спецификации . Это дает вам полную абстракцию, которую также можно проверить.)

Джгауффин
источник
6
Отсутствие доступа к IQueryable приводит к ограниченному поиску, и люди в конечном итоге создают больше методов Get для разных типов запросов, и в конечном итоге это делает репозиторий более сложным.
Akash Kava
3
вы вообще не решили основную проблему: предоставление IQueryable через репозиторий не является полной абстракцией.
jgauffin 08
1
Иметь объект запроса, который содержит всю необходимую инфраструктуру для выполнения сам по себе, является правильным решением. Вы даете ему поля, которые являются условиями поиска, и он возвращает список результатов. Внутри QO вы можете делать все, что хотите. И это интерфейс, который так легко протестировать. Смотрите мой пост выше. Это лучшее.
h.alex
2
Лично я думаю, что также имеет смысл реализовать интерфейс IQueryable <T> в классе Repository, а не раскрывать базовый набор в одном из его членов.
dark_perfect 02
3
@yat: одно репо на совокупный корень. Но imho это не совокупный корень и совокупность таблиц, а просто совокупный корень и агрегаты . Фактическое хранилище может использовать только одну таблицу или несколько из них, то есть не может быть однозначного сопоставления между каждым агрегатом и таблицей. Я использую репозитории, чтобы упростить и удалить любые зависимости базового хранилища.
jgauffin 02
29

ИМО, и 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>)));
        }
    }
}
qujck
источник
Интересно, что этот ответ все еще актуален в 2020 году. С тех пор мало что изменилось. Не так много методов для замены RP
Волкан Гювен
11

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

Поддельный репозиторий для модульных тестов, действительно ли он нам нужен?

Мы позволяем EF взаимодействовать с тестовой БД в модульных тестах, чтобы протестировать нашу бизнес-логику прямо на тестовой БД SQL. Я не вижу никакой пользы в том, чтобы издеваться над каким-либо шаблоном репозитория. Что действительно не так при выполнении модульных тестов с тестовой базой данных? Поскольку это массовые операции невозможны, и мы пишем необработанный SQL. SQLite в памяти - идеальный кандидат для выполнения модульных тестов с реальной базой данных.

Ненужная абстракция

Вы хотите создать репозиторий только для того, чтобы в будущем вы могли легко заменить EF на NHbibernate и т. Д. Или что-то еще? Звучит отличный план, но действительно ли он рентабелен?

Linq убивает юнит-тесты?

Я хотел бы увидеть любые примеры того, как он может убивать.

Внедрение зависимостей, IoC

Ух ты, это отличные слова, конечно, они отлично смотрятся в теории, но иногда приходится выбирать между отличным дизайном и отличным решением. Мы все это использовали, и в итоге бросили все в мусор и выбрали другой подход. Размер против скорости (размер кода и скорость разработки) имеет огромное значение в реальной жизни. Пользователям нужна гибкость, им все равно, хорош ли ваш код с точки зрения DI или IoC.

Если вы не создаете Visual Studio

Все эти великолепные разработки необходимы, если вы создаете сложную программу, такую ​​как Visual Studio или Eclipse, которая будет разрабатываться многими людьми и должна иметь широкие возможности настройки. Все великие паттерны разработки стали очевидны после многих лет разработки, через которые прошли эти IDE, и они развивались в месте, где все эти замечательные паттерны проектирования так важны. Но если вы занимаетесь простым веб-расчетом заработной платы или простым бизнес-приложением, лучше, чтобы вы со временем развивались в процессе разработки, вместо того, чтобы тратить время на его создание для миллионов пользователей, где оно будет развернуто только для сотен пользователей.

Репозиторий в виде отфильтрованного представления - 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, а DbSets - это репозитории.

Что касается того, как работать с уровнем данных, я просто работаю непосредственно с объектом DbContext, для сложных запросов я создам методы расширения для запроса, которые можно использовать повторно.

Я считаю, что у Айенде также есть несколько сообщений о том, насколько плохо абстрагироваться от операций CUD.

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

Джош
источник
Итак, насколько обширны методы расширения? Допустим, мне нужно получить состояние другого объекта в моем расширении? Это моя самая большая проблема прямо сейчас. Не могли бы вы показать несколько примеров методов расширения?
Dejan.S 01
ayende.com/blog/153473/… и ayende.com/blog/153569/… . (Это обзоры архитектуры (Framework?) Под названием s # arp lite. В основном хорошая, но он не согласен с репозиториями и абстракциями CUD).
Джош
Его основанный на NHibernate. У вас нет примеров использования EF? И снова, когда мне нужно вызвать другой объект, как это лучше всего сделать в методе статического расширения?
Dejan.S 02
3
Это все хорошо до тех пор, пока свойство вашего объекта домена не будет обновлено данными, которые не хранятся в вашей базе данных; или вам нужно перейти к технологии, которая более производительна, чем ваша раздутая ORM. Упс! 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 02
Я привык делать сервисный слой для бизнес-логики. Я не совсем уверен, что слежу за тем, что вы делаете с ContentService, пожалуйста, уточните. Было бы плохой практикой делать вспомогательные классы как «уровень обслуживания»?
Dejan.S 02
Под «уровнем сервиса» я имел в виду «сервисы приложений». Они могут использовать репозиторий и любую другую общедоступную часть уровня домена. «Уровень обслуживания» - неплохая практика, но я бы не стал создавать класс XService только для того, чтобы предоставить результат List <X>. Поле для комментариев кажется слишком коротким для подробного описания услуг, извините.
mikalai 02
Что, если, скажем, расчет корзины, и вам нужно получить параметры настроек приложения и параметры конкретного клиента для выполнения расчета, и это повторно используется в нескольких местах в приложении. Как вы справитесь с этой ситуацией? вспомогательный класс или служба приложения?
Dejan.S 03
1

Repository (или однако один хочет называть его) в это время для меня в основном о абстрагируясь от персистенции слоя.

Я использую его вместе с объектами запросов, поэтому у меня нет связи с какой-либо конкретной технологией в моих приложениях. А также это значительно облегчает тестирование.

Итак, я склонен

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 вам потребуется контекст в абстрактной базе, а не сеанс. Но, конечно, ifc будет таким же.

Таким образом, сами запросы инкапсулируются и легко тестируются. Лучше всего то, что мой код полагается только на интерфейсы. Все очень чисто. Объекты домена (бизнес) - это всего лишь объекты, например, нет смешивания обязанностей, например, при использовании шаблона активной записи, который трудно тестировать и смешивает код доступа к данным (запроса) в объекте домена и при этом смешивает проблемы (объект, который выбирает сам??). Все по-прежнему могут создавать POCO для передачи данных.

В общем, этот подход обеспечивает многократное использование кода и простоту, при этом я ничего не могу себе представить. Любые идеи?

И большое спасибо Айенде за его отличные посты и постоянную преданность делу. Здесь его идеи (объект запроса), а не мои.

h.alex
источник
1
Сущности персистентности (ваши POCO) НЕ являются бизнес-объектами / объектами домена. А цель репозитория - отделить деловой (любой) уровень от персистентности.
MikeSW
Я не вижу сцепления. В некоторой степени согласен с частью POCO, но все равно. Ничто не мешает вам иметь «настоящие» POCO и продолжать использовать этот подход.
h.alex
1
Сущности вовсе не обязательно должны быть тупыми POCO. Фактически, моделирование бизнес-логики в Entities - это то, чем все время занимается команда DDD. Этот стиль разработки очень хорошо сочетается с NH или EF.
Крис
1

Для меня это простое решение с относительно небольшим количеством факторов. Факторы:

  1. Репозитории предназначены для доменных классов.
  2. В некоторых моих приложениях классы домена совпадают с классами сохраняемости (DAL), в других - нет.
  3. Когда они совпадают, EF уже предоставляет мне репозитории.
  4. EF обеспечивает отложенную загрузку и IQueryable. Мне это нравится.
  5. Абстрагирование / 'фасад' / повторная реализация репозитория через EF обычно означает потерю ленивого и IQueryable

Итак, если мое приложение не может оправдать № 2, разделить модели домена и данных, то я обычно не буду беспокоиться о № 5.

Меловой
источник