EF6 Code First具有通用存储库和依赖注入和SoC

时间:2014-12-17 14:24:41

标签: c# asp.net-mvc dependency-injection ef-code-first entity-framework-6

经过大量阅读并尝试使用Entity Framework最新稳定版本(6.1.1)。

我在阅读有关是否一般使用EF6EF的存储库时遇到很多矛盾,因为它的DbContext已经提供了存储库, DbSet UoW,开箱即用。

首先让我解释一下我的解决方案在项目方面所包含的内容,然后我会回到这个矛盾中。

它有一个类库项目和一个asp.net-mvc项目。 lib类项目是数据访问,MigrationsCode First启用了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); }

在我的类lib项目中,我有一个通用存储库:

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。因此,对于asp-net-mvc,我使用Ninjectprivate static void RegisterServices(IKernel kernel) { kernel.Bind<ApplicationDbContext>().ToSelf().InRequestScope(); } 项目中设置了以下内容:

ApplicationDbContext

这将通过构造函数注入将EF注入适用的上层类。

现在回到矛盾中。

如果我们不再需要存储库,因为Separation of Concern已经开箱即用,我们如何做asp.net-mvc(标题中缩写为SoC)?

现在纠正我,如果我错了,但听起来像我只需要做所有的数据访问逻辑/计算(比如添加,获取,更新,删除和一些自定义逻辑/计算)如果我没有添加存储库,则{{1}}项目中的(特定于实体))。

对此事的任何启示都非常感激。

1 个答案:

答案 0 :(得分:40)

一点点解释有望解决你的困惑。存储库模式用于抽象出数据库连接和查询逻辑。 ORM(对象关系映射器,如EF)已经以这样或那样的形式存在,以至于许多人已经忘记或者从未有过处理充斥着SQL查询和语句的意大利面条代码的巨大喜悦和乐趣。时间是,如果你想查询数据库,你实际上负责疯狂的事情,如启动连接和实际从以太构造SQL语句。存储库模式的目的是为您提供一个单独的位置来放置所有这些肮脏,远离您美丽的原始应用程序代码。

快进到2014年,实体框架和其他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发生了变化,那么您仍然处于小河中。

<强>更新

设置非常相似;差异主要在于您如何使用服务而不是存储库。也就是说,我甚至不会让它依赖于实体。换句话说,您实际上每个上下文都有一个服务,而不是每个实体。

与往常一样,从界面开始:

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>();