[英]EF6 Code First with generic repository and Dependency Injection and SoC
经过大量阅读并尝试使用Entity Framework
最新的稳定版本(6.1.1)。
我读了大量关于是否要使用与库矛盾EF6
或EF
一般,因为它DbContext
已经提供了一个资源库和DbSet
的UoW
,开箱即用。
让我首先解释一下我的解决方案在项目方面所包含的内容,然后我将回到这个矛盾中。
它有一个类库项目和一个asp.net-mvc
项目。 类lib项目是数据访问,并且为Code First
启用了Migrations
。
在我的类lib项目中,我有一个通用的存储库:
public interface IRepository<TEntity> where TEntity : class
{
IEnumerable<TEntity> Get();
TEntity GetByID(object id);
void Insert(TEntity entity);
void Delete(object id);
void Update(TEntity entityToUpdate);
}
以下是它的实现:
public class Repository<TEntity> where TEntity : class
{
internal ApplicationDbContext context;
internal DbSet<TEntity> dbSet;
public Repository(ApplicationDbContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get()
{
IQueryable<TEntity> query = dbSet;
return query.ToList();
}
public virtual TEntity GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
这里有几个实体:
public DbSet<User> User{ get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<UserOrder> UserOrders { get; set; }
public DbSet<Shipment> Shipments { get; set; }
我不重复自己,但是,使用EF6
你不再传递存储库,而是使用DbContext
。 所以对于DI
我在使用Ninject
的asp-net-mvc
项目中设置了以下内容:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ApplicationDbContext>().ToSelf().InRequestScope();
}
这将通过构造函数注入将ApplicationDbContext
注入适用的上层类。
现在又回到了矛盾之中。
如果我们不再需要存储库,因为EF
已经开箱即用,我们如何进行Separation of Concern
(标题中缩写为SoC)?
现在纠正我,如果我错了,但听起来像我只需要做所有数据访问逻辑/计算(如添加,获取,更新,删除和一些自定义逻辑/计算(特定于实体))在asp.net-mvc
项目中,如果我不添加存储库。
关于此事的任何启示都非常感激。
一点点解释有望解决你的困惑。 存储库模式用于抽象出数据库连接和查询逻辑。 ORM(对象关系映射器,如EF)已经以这样或那样的形式存在,以至于许多人已经忘记或者从未有过处理充斥着SQL查询和语句的意大利面条代码的巨大喜悦和乐趣。 时间是,如果你想查询数据库,你实际上负责疯狂的事情,如启动连接和实际从以太构造SQL语句。 存储库模式的重点是为您提供一个单独的位置来放置所有这些肮脏,远离您美丽的原始应用程序代码。
快进到2014年,Entity Framework和其他ORM 是您的存储库。 所有的SQL逻辑都整齐地远离你的窥探,而你的代码中有一个很好的编程API。 在一个方面,这是足够的抽象。 它唯一没有涉及的是对ORM本身的依赖。 如果您以后决定要为NHibernate甚至Web API之类的东西切换实体框架,那么您必须对您的应用程序进行手术。 因此,添加另一层抽象仍然是一个好主意,但不是一个存储库,或者至少让我们说一个典型的存储库。
您拥有的存储库是典型的存储库。 它只是为Entity Framework API方法创建代理。 您调用repo.Add
并且存储库调用context.Add
。 坦率地说,这是荒谬的,这就是为什么很多人,包括我自己,都说不要在实体框架中使用存储库 。
所以,你应该怎么做? 创建服务,或者最好称之为“类似服务的类”。 当开始讨论与.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
等。
现在,我喜欢使用一个受保护的方法,它返回一个我的所有服务方法都可以使用的查询。 通过var dbSet = context.Set<YourEntity>();
每次都必须初始化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>();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.