Ответ 1
Небольшое объяснение, надеюсь, очистит ваше замешательство. Шаблон репозитория существует для абстрактного соединения с базой данных и логики запросов. ORM (объектно-реляционные картографы, такие как EF) были в той или иной форме настолько длинными, что многие люди забыли или никогда не испытывали огромной радости и удовольствия от работы с кодом спагетти, замусоренным SQL-запросами и утверждениями. Было время, что если вы хотите запросить базу данных, вы фактически несете ответственность за сумасшедшие вещи, такие как инициирование соединения и фактическое построение операторов SQL из эфира. Цель шаблона репозитория заключалась в том, чтобы дать вам одно место, чтобы положить всю эту гадость, подальше от вашего красивого нетронутого кода приложения.
Ускоренная пересылка на 2014 год, платформа Entity Framework и другие ORM - это ваш репозиторий. Вся логика SQL аккуратно упакована из ваших любопытных глаз, и вместо этого у вас есть хороший программный API для использования в вашем коде. В одном отношении это достаточно абстракция. Единственное, на что он не распространяется, - это зависимость от самого ORM. Если позже вы решите, что хотите отключить Entity Framework для чего-то вроде NHibernate или даже веб-API, вам нужно сделать операцию в своем приложении, чтобы сделать это. В результате добавление еще одного уровня абстракции по-прежнему является хорошей идеей, но просто не репозиторием или, по крайней мере, пусть типичным репозиторием.
У репозитория у вас есть типичный репозиторий. Он просто создает прокси для методов API Entity Framework. Вы вызываете repo.Add
, а репозиторий вызывает context.Add
. Это, честно говоря, смешно, и поэтому многие, включая меня, говорят, что не используют репозитории с Entity Framework.
Так что вы должны делать? Создавайте службы, или, возможно, лучше всего называйте "сервисные классы". Когда услуги начинают обсуждаться в отношении .NET, внезапно вы говорите о любых вещах, которые совершенно не имеют отношения к тому, что мы обсуждаем здесь. Класс, подобный сервису, подобен сервису, поскольку он имеет конечные точки, которые возвращают определенный набор данных или выполняют очень специфическую функцию для некоторого набора данных. Например, в то время как с типичным репозиторием вы обнаружите, что вы сами делаете такие вещи, как:
articleRepo.Get().Where(m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now).OrderByDescending(o => o.PublishDate)
Ваш класс обслуживания будет работать следующим образом:
service.GetPublishedArticles();
Смотрите, вся логика для того, что квалифицируется как "опубликованная статья", аккуратно содержится в методе конечных точек. Кроме того, в репозитории вы все еще подвергаете воздействию базового API. Легче переключаться с чем-то другим, потому что базовое хранилище данных абстрагируется, но если API для запроса в этот хранилище данных изменится, вы все еще будите.
UPDATE
Настройка будет очень похожей; разница в основном заключается в том, как вы используете службу против репозитория. А именно, я бы даже не сделал его зависимым от сущности. Другими словами, у вас по существу есть служба для каждого контекста, а не для объекта.
Как всегда, начните с интерфейса:
public interface IService
{
IEnumerable<Article> GetPublishedArticles();
...
}
Затем ваша реализация:
public class EntityFrameworkService<TContext> : IService
where TContext : DbContext
{
protected readonly TContext context;
public EntityFrameworkService(TContext context)
{
this.context = context;
}
public IEnumerable<Article> GetPublishedArticles()
{
...
}
}
Тогда все начинает становиться немного волосатым. В методе примера вы можете просто ссылаться на DbSet
напрямую, т.е. context.Articles
, но это подразумевает знание имен DbSet
в контексте. Лучше использовать context.Set<TEntity>()
для большей гибкости. Прежде чем переходить на поезда слишком много, я хочу указать, почему я назвал это EntityFrameworkService
. В вашем коде вы всегда ссылаетесь только на свой IService
интерфейс. Затем, используя контейнер для инъекций зависимости, вы можете заменить EntityFrameworkService<YourContext>
на это. Это открывает возможность создания других поставщиков услуг, например, WebApiService
и т.д.
Теперь мне нравится использовать один защищенный метод, который возвращает запрос, который могут использовать все мои сервисные методы. Это избавляет от большого количества трещин, таких как необходимость каждый раз инициализировать экземпляр DbSet
через var dbSet = context.Set<YourEntity>();
. Это будет выглядеть примерно так:
protected virtual IQueryable<TEntity> GetQueryable<TEntity>(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = null,
int? skip = null,
int? take = null)
where TEntity : class
{
includeProperties = includeProperties ?? string.Empty;
IQueryable<TEntity> query = context.Set<TEntity>();
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
query = orderBy(query);
}
if (skip.HasValue)
{
query = query.Skip(skip.Value);
}
if (take.HasValue)
{
query = query.Take(take.Value);
}
return query;
}
Обратите внимание, что этот метод, во-первых, защищен. Подклассы могут использовать его, но это определенно не должно быть частью публичного API. Весь смысл этого упражнения заключается в том, чтобы не подвергать запросов. Во-вторых, он общий. В других словах он может обрабатывать любой тип, который вы бросаете на него, если в нем есть что-то в этом контексте.
Затем, в нашем маленьком примере, вы в конечном итоге сделаете что-то вроде:
public IEnumerable<Article> GetPublishedArticles()
{
return GetQueryable<Article>(
m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now,
m => m.OrderByDescending(o => o.PublishDate)
).ToList();
}
Другим опрятным подходом к этому подходу является способность иметь общие методы обслуживания с использованием интерфейсов. Позвольте сказать, что я хотел иметь один способ получить опубликованное что-либо. Я мог бы иметь такой интерфейс, как:
public interface IPublishable
{
PublishStatus Status { get; set; }
DateTime PublishDate { get; set; }
}
Затем любые объекты, которые могут быть доступны для публикации, просто реализуют этот интерфейс. Теперь вы можете сделать следующее:
public IEnumerable<TEntity> GetPublished<TEntity>()
where TEntity : IPublishable
{
return GetQueryable<TEntity>(
m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now,
m => m.OrderByDescending(o => o.PublishDate)
).ToList();
}
И затем в вашем коде приложения:
service.GetPublished<Article>();