繁体   English   中英

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

[英]EF6 Code First with generic repository and Dependency Injection and SoC

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

我读了大量关于是否要使用与库矛盾EF6EF一般,因为它DbContext已经提供了一个资源库和DbSetUoW ,开箱即用。

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

它有一个类库项目和一个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我在使用Ninjectasp-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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM