且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

EF6 code首先与通用存储库和依赖注入和SoC

更新时间:2023-02-13 15:41:08

一点解释有望清理你的困惑。 Repository模式存在抽象掉数据库连接和查询逻辑。奥姆斯(对象关系映射器,如EF)就出现在这种或那种形式这么久,很多人已经忘记或从未有过的巨大的喜悦和处理与SQL查询和语句充斥意大利面条code的乐趣。时间,如果你想查询数据库,你的疯狂的事情就像发起连接,实际上构建由乙醚SQL语句的实际负责。库模式的一点是给你一个地方把所有这个龌龊,远离你的美丽的原始应用程序code。

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年,实体框架和其他奥姆斯的的资源库。所有的SQL逻辑是从窥探收拾整齐了,而是你有一个很好的编程API在code使用。一方面,这就够了抽象。它不包括的唯一的事情就是对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)

您服务类将工作,如:

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

    ...
}

然后,您的实现:

Then, your implementation:

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&LT; TEntity&GT;(),有更多​​的灵活性。以前我跳火车太多,但,我想指出为什么我命名为 EntityFrameworkService 。在您的code,你就永远只能引用您的 IService 接口。然后,通过您的依赖注入容器,可以替代 EntityFrameworkService&LT; YourContext&GT; 为。这开辟了创建其他服务提供商,如可能的能力 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.

现在,我喜欢用一个返回我所有的服务方法可以利用可查询的一个受保护的方法。这摆脱了大量的克鲁夫特的就像通过每次初始化 DbSet 实例 VAR dbSet = context.Set&LT; YourEntity&GT;(); 。这看起来有点像:

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的一部分。这个练习的要点是不要暴露queryables。其次,它是通用的。在otherwords,它可以处理你在它只要有东西在上下文扔任何类型的。

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

另外一个巧妙的方法这种方法是有利用接口通用服务方法的能力。比方说,我希望能够有一个方法来获取已发布的任何的。我能有这样一个接口:

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

然后在你的应用程序code:

And then in your application code:

service.GetPublished<Article>();