使用DbContext和DbSet代替实现仓储库和工作单元模式。

3

我看过很多有关实现存储库和工作单元的文章。我也看到了一些文章,谈到这样做只是增加了额外的复杂性,因为DbContext已经在使用存储库和工作单元模式。

我将重构一个应用程序,它几乎为每个实体都有一个存储库,并希望尽可能地减少复杂性。

有人能解释/提供文章/博客等,讲解如何使用DbContext而不是我自己的存储库吗?


2
http://rob.conery.io/2014/03/04/repositories-on-top-unitofwork-are-not-a-good-idea/ - Matt
https://github.com/jbogard/presentations/blob/master/ORMs/Orms.pptx - Matt
https://github.com/carbonrobot/fullstackef - Charlie Brown
我认为仓库/实体是一种反模式,大多数人认为这种模式的优点实际上属于服务层。仓库的定义甚至是“使用类似集合的接口访问域对象,调解域和数据映射层之间关系”的,这对我来说意味着每个仓库可以有多个实体。 - Charlie Brown
1个回答

5
罗布·科纳利是个聪明的人,但在这件事上我不同意他的看法。他建议的命令/查询方法只是将查询逻辑从操作中移除(这也算是一种方法,但不算很完美)。仍然没有真正的抽象化。此外,基本控制器方法也不是很好。虽然数据访问方法(这里采用 ORM)被抽象到代码中的一个位置,使得未来的更改变得容易了一些,但它并没有为使用该数据层的 API 进行抽象化,所以它几乎是无意义的。它真正挽救你的只是省去了在每个控制器顶部放置 private readonly AppContext context = new AppContext(); 的步骤。也许你可以将两者结合起来,但这样你仍然需要修改每一个查询类,如果你的数据层发生变化的话。
我认为这里的主要问题是每个人都试图实现不同的目标。罗布建议的方法旨在保持 DRY。而我个人在抽象化数据层方面的目标则在于能够轻松地在以后切换数据访问方法。也许这是因为过去我曾因选择某种获取数据的方法而最终导致长期效果不佳而备受打击。但至少我们都可以认同的是,传统实现存储库的方式真是不好。
事实上,这是一个没有真正答案的问题。在某种程度上,你必须做最适合你和你的应用程序的事情。我采取的方法与存储库模式有点相似,我使用通用方法而不是通用类。类似以下内容:
public class Repository : IRepository
{
    protected readonly DbContext context;

    public Repository(DbContext context)
    {
        this.context = context;
    }

    public IEnumerable<TEntity> GetAll<TEntity>()
    {
        var dbSet = context.Set<TEntity>;
        return dbSet.ToList();
    }

    ...
}

我的实际类比那个复杂得多,但这足以说明主要观点。首先,注入上下文。这是我强烈不同意Rob的一个领域。也许如果您在处理上下文时玩得快而松散,您可能不知道“它来自哪里”,但我使用依赖注入容器为每个请求创建一个上下文实例。换句话说,我确切地知道它来自哪里。

其次,因为这是一个带有通用方法的标准老类,我不需要在控制器操作中新建一堆它们。我也不必为每个实体定义单独的存储库类。我只需将此依赖项注入到我的控制器中并执行即可:

public class FooController : Controller
{
    private readonly IRepository repo;

    public FooController(IRepository repo)
    {
        this.repo = repo;
    }

    ...
}

然后,如果我想获取一些Foo,我只需要执行以下操作:
repo.GetAll<Foo>();

如果我想要一些Bar

repo.GetAll<Bar>()

然后,你可以通过泛型约束开始做一些真正有趣的事情。比如说,我想只提取“已发布”的项目。我所需要的就是一个像这样的接口:

public interface IPublishable
{
    PublishStatus Status { get; }
    DateTime? PublishDate { get; }
    DateTime? ExpireDate { get; }
}

然后,我只需让我想要“可发布”的实体实现此接口或继承实现它的抽象类。一旦所有设置完成,我就可以在我的存储库中执行以下操作:

public IEnumerable<TEntity> GetAllPublished<TEntity>()
    where TEntity : IPublishable
{
    var dbSet = context.Set<TEntity>();
    return dbSet.Where(m =>
        m.Status == PublishStatus.Published &&
        m.PublishDate.HasValue && m.PublishDate.Value <= DateTime.Now &&
        (!m.ExpireDate.HasValue || m.ExpireDate.Value > DateTime.Now)
    ).ToList();
}

现在,我有一个方法在一个存储库中,可以仅提取实现了IPublishable接口的任何实体的已发布项。代码重复最少,并且更重要的是,如果我需要将数据访问层切换到其他东西,比如不同的ORM甚至Web API,我只需要更改这个存储库类。所有其余的代码都可以愉快地运行,就好像什么也没有发生。


我认为大部分的代码应该放在服务层,而不是存储库中。这样存储库就可以保持通用性。(注:服务层指的是服务层,而不是WCF或Web API服务)。 - Charlie Brown
是的,我大多数时候都保持“Repository”命名约定以使其相关联。你可以随意将其称为“Service”或其他任何名称。令人惊讶的是,我从未在任何地方看到过像我这样做的参考。如果您知道其中之一,我很想看到链接。因此,我有些不知所措该如何准确地称呼它。它并不真正是一个存储库,但至少就“服务模式”而言,它也不完全是一个服务。在某些方面,它实际上与Web API的工作方式非常相似,只是没有HTTP请求。 - Chris Pratt
这与PofEAA定义的服务层相同。类似于我在https://github.com/carbonrobot/fullstackef中使用的内容。 - Charlie Brown
好的,我得看看这两个。说实话,它感觉更像是服务而不是其他什么。事实上,以前我在跟人们谈论它时曾经把它描述为一种服务,但后来我读了微软对服务模式的定义,发现它与我的理解相去甚远,所以我认为最好不要混淆术语。不幸的是,即使马丁·福勒称之为服务,我认为微软的内涵会在人们的脑海中留下印象。 - Chris Pratt
同意,微软对“服务”一词的使用已经非常普遍,现在很难再将它们区分开来了。 - Charlie Brown

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接