具有通用存储库和依赖注入和 SoC 的 EF6 Code First [英] EF6 Code First with generic repository and Dependency Injection and SoC

查看:19
本文介绍了具有通用存储库和依赖注入和 SoC 的 EF6 Code First的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

After a lots of reading and trying things out with Entity Framework latest stable version (6.1.1).

我正在阅读很多关于是否使用 EF6EF 的存储库的矛盾,因为它的 DbContext 已经提供开箱即用的存储库和 DbSet UoW.

I'm reading lots of contradictions about whether or not to use repositories with EF6 or EF in general, because it's DbContext already provides a repository and DbSet the UoW, out of the box.

让我先解释一下我的解决方案在项目方面包含的内容,然后我会回到矛盾之处.

Let me first explain what my solution contains in terms of project and then I'll comeback at the contradiction.

它有一个类库项目和一个asp.net-mvc项目.类库项目是数据访问,并且为 Code First 启用了 Migrations.

It has a class library project, and an asp.net-mvc project. The class lib project being the data access and where Migrations are enabled for Code First.

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

In my class lib project I have a generic repository:

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 项目中设置了以下内容:

I don't what to repeat myself but, with EF6 you don't pass repositories anymore, but the DbContext instead. So for DI I've set up the following in the asp-net-mvc project using Ninject:

private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<ApplicationDbContext>().ToSelf().InRequestScope();
}

这将通过构造函数注入将 ApplicationDbContext 注入到适用的上层类中.

And this will inject the ApplicationDbContext via constructor injection to upper layer classes where applicable.

现在回到矛盾.

如果我们不再需要存储库,因为 EF 已经提供了开箱即用的存储库,我们如何进行 Separation of Concern(标题中缩写为 SoC)?

If we don't need a repository anymore because EF already provides that out of the box, how do we do Separation of Concern (abbreviated as SoC in title)?

如果我错了,现在纠正我,但听起来我只需要执行所有数据访问逻辑/计算(例如添加、获取、更新、删除和一些自定义逻辑/计算(实体具体))在 asp.net-mvc 项目中,如果我不添加存储库.

Now correct me if I'm wrong, but it sounds to me like I just need to do all the data access logic/calculations (like adding, fetching, updating, deleting and some custom logic/calculations here and there (entity specific)) in the asp.net-mvc project, if I don't add a repository.

非常感谢您对此事的任何了解.

Any light on this matter is really appreciated.

推荐答案

一点解释希望能消除你的困惑.存储库模式的存在是为了抽象出数据库连接和查询逻辑.ORM(对象关系映射器,如 EF)以一种或另一种形式存在已久,以至于许多人已经忘记或从未享受过处理充斥着 SQL 查询和语句的意大利面条式代码的巨大乐趣和乐趣.时间是,如果你想查询一个数据库,你实际上要负责一些疯狂的事情,比如启动一个连接,并实际从 ether 构造 SQL 语句.存储库模式的重点是为您提供一个单一的地方来放置所有这些肮脏的东西,远离您美丽的原始应用程序代码.

A little explanation will hopefully clear up your confusion. The repository pattern exists to abstract away database connection and querying logic. ORMs (object-relational mappers, like EF) have been around in one form or another so long that many people have forgotten or never had the immense joy and pleasure of dealing with spaghetti code littered with SQL queries and statements. Time was that if you wanted to query a database, you were actually responsible for crazy things like initiating a connection and actually constructing SQL statements from ether. The point of the repository pattern was to give you a single place to put all this nastiness, away from your beautiful pristine application code.

快进到 2014 年,实体框架和其他 ORM 是您的存储库.所有的 SQL 逻辑都被整齐地打包,远离你的窥探,取而代之的是,你有一个很好的编程 API 可以在你的代码中使用.一方面,这已经足够抽象了.它唯一没有涵盖的是对 ORM 本身的依赖.如果您后来决定要为 NHibernate 甚至 Web API 之类的东西切换实体框架,那么您必须对您的应用程序进行手术才能这样做.因此,添加另一层抽象仍然是一个好主意,但不是存储库,或者至少可以说是一个典型的存储库.

Fast forward to 2014, Entity Framework and other ORMs are your repository. All the SQL logic is packed neatly away from your prying eyes, and instead you have a nice programmatic API to use in your code. In one respect, that's enough abstraction. The only thing it doesn't cover is the dependency on the ORM itself. If you later decide you want to switch out Entity Framework for something like NHibernate or even a Web API, you've got to do surgery on your application to do so. As a result, adding another layer of abstraction is still a good idea, but just not a repository, or at least let's say a typical repository.

您拥有的存储库是一个典型 存储库.它只是为实体框架 API 方法创建代理.您调用 repo.Add 并且存储库调用 context.Add.坦率地说,这很荒谬,这就是为什么包括我自己在内的许多人说不要将存储库与实体框架一起使用.

The repository you have is a typical repository. It merely creates proxies for the Entity Framework API methods. You call repo.Add and the repository calles context.Add. It's, frankly, ridiculous, and that's why many, including myself, say don't use repositories with Entity Framework.

那么应该你怎么办?创建服务,或者最好将其称为类服务类".当开始讨论与 .NET 相关的服务时,突然之间,您谈论的是与我们在此讨论的内容完全无关的各种事物.类服务类与服务类似,因为它具有返回特定数据集或对某些数据集执行非常特定功能的端点.例如,虽然使用典型的存储库,您会发现自己在做以下事情:

So what should you do? Create services, or perhaps it's best said as "service-like classes". When services start being discussed in relation to .NET, all of sudden you're talking about all kinds of things that are completely irrelevant to what we're discussing here. A service-like class is like a service in that it has endpoints that return a particular set of data or perform a very specific function on some set of data. For example, whereas with a typical repository you would find your self doing things like:

articleRepo.Get().Where(m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now).OrderByDescending(o => o.PublishDate)

您的服务类的工作方式如下:

Your service class would work like:

service.GetPublishedArticles();

看,符合已发表文章"的所有逻辑都巧妙地包含在端点方法中.此外,使用存储库,您仍在公开底层 API.更容易切换到其他东西,因为基本数据存储是抽象的,但如果用于查询该数据存储的 API 发生变化,你仍然是一条小河.

See, all the logic for what qualifies as a "published article" is neatly contain in the endpoint method. Also, with a repository, you're still exposing the underlying API. It's easier to switch out with something else because the base datastore is abstracted, but if the API for querying into that datastore changes you're still up a creek.

更新

设置将非常相似;区别主要在于您使用服务与存储库的方式.也就是说,我什至不会让它依赖于实体.换句话说,你基本上每个上下文都有一个服务,而不是每个实体.

Set up would be very similar; the difference is mostly in how you use a service versus a repository. Namely, I wouldn't even make it entity dependent. In other words, you'd essentially have a service per context, not per entity.

一如既往,从界面开始:

As always, start with an interface:

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(),以获得更大的灵活性.不过,在我过多地跳火车之前,我想指出我为什么将其命名为 EntityFrameworkService.在您的代码中,您只会引用您的 IService 接口.然后,通过你的依赖注入容器,你可以用 EntityFrameworkService<YourContext> 代替它.这开启了创建其他服务提供者的能力,例如 WebApiService 等.

Then, things start to get a little hairy. In the example method, you could simply reference the DbSet directly, i.e. context.Articles, but that implies knowledge about the DbSet names in the context. It's better to use context.Set<TEntity>(), for more flexibility. Before I jump trains too much though, I want to point out why I named this EntityFrameworkService. In your code, you would only ever reference your IService interface. Then, via your dependency injection container, you can substitute EntityFrameworkService<YourContext> for that. This opens up the ability to create other service providers like maybe WebApiService, etc.

现在,我喜欢使用一个受保护的方法,它返回一个我所有服务方法都可以使用的可查询对象.这消除了很多麻烦,例如每次都必须通过 var dbSet = context.Set(); 初始化 DbSet 实例.看起来有点像:

Now, I like to use a single protected method that returns a queryable that all my service methods can utilize. This gets rid of a lot of the cruft like having to initialize a DbSet instance each time via var dbSet = context.Set<YourEntity>();. That would look a little like:

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 的一部分.这个练习的重点是不暴露可查询对象.其次,它是通用的.换句话说,它可以处理你扔给它的任何类型,只要它的上下文中有东西.

Notice that this method is, first, protected. Subclasses can utilize it, but this should definitely not be part of the public API. The whole point of this exercise is to not expose queryables. Second, it's generic. In otherwords, it can handle any type you throw at it as long as there's something in the context for it.

然后,在我们的小示例方法中,您最终会执行以下操作:

Then, in our little example method, you'd end up doing something like:

public IEnumerable<Article> GetPublishedArticles()
{
    return GetQueryable<Article>(
        m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now,
        m => m.OrderByDescending(o => o.PublishDate)
    ).ToList();
}

这种方法的另一个巧妙技巧是能够使用接口提供通用服务方法.假设我希望能够有一种方法来获取已发布的anything.我可以有这样的界面:

Another neat trick to this approach is the ability to have generic service methods utilizing interfaces. Let's say I wanted to be able to have one method to get a published anything. I could have an interface like:

public interface IPublishable
{
    PublishStatus Status { get; set; }
    DateTime PublishDate { get; set; }
}

然后,任何可发布的实体都会实现这个接口.有了这些,您现在可以执行以下操作:

Then, any entities that are publishable would just implement this interface. With that in place, you can now do:

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

然后在您的应用程序代码中:

And then in your application code:

service.GetPublished<Article>();

这篇关于具有通用存储库和依赖注入和 SoC 的 EF6 Code First的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆