一些解释有望消除您的困惑。存储库模式的存在是为了抽象数据库连接和查询逻辑。 ORM(对象关系映射器,如 EF)已经以一种或另一种形式存在了很长时间,以至于许多人已经忘记或从未享受过处理散布着 SQL 查询和语句的意大利面条代码的巨大乐趣和乐趣。时间是,如果你想查询数据库,你实际上要负责一些疯狂的事情,比如启动连接和实际从以太构建 SQL 语句。存储库模式的目的是为您提供一个地方来放置所有这些肮脏的东西,远离美丽的原始应用程序代码。
快进到 2014 年,实体框架和其他 ORMare你的存储库。所有 SQL 逻辑都整齐地打包在一起,远离您的窥探,而是您可以在代码中使用一个很好的编程 API。从一方面来说,这已经足够抽象了。它唯一没有涵盖的是对 ORM 本身的依赖。如果您后来决定将实体框架换成 NHibernate 甚至 Web API 之类的东西,那么您必须对您的应用程序进行手术才能做到这一点。因此,添加另一层抽象仍然是一个好主意,但不是存储库,或者至少可以说是典型的存储库。
您拥有的存储库是typical存储库。它只是为实体框架 API 方法创建代理。你打电话repo.Add
和存储库称为context.Add
。坦率地说,这很荒谬,这就是为什么许多人,包括我自己,说不要将存储库与实体框架一起使用.
So what should你做?创建服务,或者最好的说法是“类似服务的类”。当开始讨论与 .NET 相关的服务时,突然间您会谈论与我们在此讨论的内容完全无关的各种事物。类似服务的类就像服务一样,它具有返回特定数据集或对某些数据集执行非常特定的功能的端点。例如,使用典型的存储库时,您会发现自己在执行以下操作:
articleRepo.Get().Where(m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now).OrderByDescending(o => o.PublishDate)
您的服务类将像这样:
service.GetPublishedArticles();
看,所有符合“已发表文章”资格的逻辑都整齐地包含在端点方法中。此外,使用存储库,您仍然会暴露底层 API。它是easier切换到其他东西,因为基本数据存储是抽象的,但如果用于查询该数据存储的 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
, etc.
现在,我喜欢使用单个受保护的方法,该方法返回我的所有服务方法都可以利用的可查询对象。这消除了很多麻烦,比如必须初始化一个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();
}
这种方法的另一个巧妙技巧是能够利用接口提供通用服务方法。假设我希望能够有一种方法来发布anything。我可以有一个像这样的界面:
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>();