当前位置: 首页 > 知识库问答 >
问题:

不使用存储库模式,按原样使用 ORM (EF)

徐弘图
2023-03-14

我一直使用Repository模式,但对于我的最新项目,我想看看是否可以完善它的使用和“工作单元”的实现。我越开始挖掘,我就开始问自己:“我真的需要它吗?”

现在这一切都始于对 Stackoverflow 的几条评论,并追溯到 Ayende Rahien 在他的博客上的帖子,其中有 2 条具体,

    < li >存储库是新的单一存储库 问:没有仓库的生活值得活下去吗

这可能会永远被谈论,这取决于不同的应用程序。我想知道什么,

  1. 此方法是否适用于实体框架项目?
  2. 使用这种方法的业务逻辑是否仍在服务层或扩展方法中(如下所述,我知道,扩展方法正在使用 NHib 会话)?

使用扩展方法很容易做到这一点。干净、简单且可重复使用。

public static IEnumerable GetAll(
    this ISession instance, Expression<Func<T, bool>> where) where T : class
{
    return instance.QueryOver().Where(where).List();
}

使用此方法和Ninject作为DI,我是否需要将Context设置为接口并将其注入控制器中?

共有3个答案

鄢英毅
2023-03-14

在我看来,存储库抽象和UnitOfWork抽象在任何有意义的开发中都占有非常宝贵的地位。人们会争论实现细节,但正如有许多方法可以剥猫皮一样,也有许多方法可以实现抽象。

您的问题是专门使用还是不使用以及为什么。

毫无疑问,您已经意识到您已经将这两种模式都内置到实体框架中,DbContext工作单元DbSet存储库。通常不需要对 UnitOfWork存储库本身进行单元测试,因为它们只是在您的类和底层数据访问实现之间进行便利。你会发现自己需要做的一次又一次,是在对服务的逻辑进行单元测试时模拟这两个抽象。

您可以模仿、伪造或使用外部库在进行测试的逻辑和被测试的逻辑之间添加代码依赖层(您无法控制)。

因此,一个小点是,拥有自己的 UnitOfWork存储库抽象可以在模拟单元测试时为您提供最大的控制和灵活性。

一切都很好,但对我来说,这些抽象的真正力量是它们提供了一种简单的方法来应用面向方面的编程技术并遵守SOLID原则。

因此,您有了IRepository

public interface IRepository<T>
    where T : class
{
    T Add(T entity);
    void Delete(T entity);
    IQueryable<T> AsQueryable();
}

及其实施:

public class Repository<T> : IRepository<T>
    where T : class
{
    private readonly IDbSet<T> _dbSet;
    public Repository(PPContext context) 
    {
        _dbSet = context.Set<T>();
    }

    public T Add(T entity)
    { 
        return _dbSet.Add(entity); 
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity); 
    }

    public IQueryable<T> AsQueryable() 
    {
        return _dbSet.AsQueryable();
    }
}

到目前为止没有什么不寻常的,但现在我们想添加一些日志记录 - 使用日志记录装饰器很容易。

public class RepositoryLoggerDecorator<T> : IRepository<T>
    where T : class
{
    Logger logger = LogManager.GetCurrentClassLogger();
    private readonly IRepository<T> _decorated;
    public RepositoryLoggerDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
        T added = _decorated.Add(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        return added;
    }

    public void Delete(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        _decorated.Delete(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable();
    }
}

所有这些都完成了,并且没有改变我们现有的代码。我们可以添加许多其他交叉关注点,例如异常处理、数据缓存、数据验证或其他任何东西,在我们的设计和构建过程中,我们拥有的最有价值的东西使我们能够在不改变任何现有代码的情况下添加简单的功能是我们的IRepostory抽象。

现在,我在StackOverflow上多次看到这个问题——“如何让实体框架在多租户环境中工作?”。

https://stackoverflow.com/search?q =[实体-框架]多租户

如果您有一个<code>Repository</code>抽象,那么答案是“很容易添加装饰器”

public class RepositoryTennantFilterDecorator<T> : IRepository<T>
    where T : class
{
    //public for Unit Test example
    public readonly IRepository<T> _decorated;
    public RepositoryTennantFilterDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        return _decorated.Add(entity);
    }

    public void Delete(T entity)
    {
        _decorated.Delete(entity);
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable().Where(o => true);
    }
}

IMO您应该始终在任何第三方组件上放置一个简单的抽象,这些组件将在多个地方引用。从这个角度来看,ORM是完美的候选者,因为它在我们的许多代码中都有引用。

当有人说“我为什么要在这个或那个第三方库上有一个抽象(例如存储库)”时,通常会想到的答案是“你为什么不呢?

P.S. Decorators使用IoC容器应用起来非常简单,比如SimpleInjector。

[TestFixture]
public class IRepositoryTesting
{
    [Test]
    public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
    {
        Container container = new Container();
        container.RegisterLifetimeScope<PPContext>();
        container.RegisterOpenGeneric(
            typeof(IRepository<>), 
            typeof(Repository<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryLoggerDecorator<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryTennantFilterDecorator<>));
        container.Verify();

        using (container.BeginLifetimeScope())
        {
            var result = container.GetInstance<IRepository<Image>>();

            Assert.That(
                result, 
                Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
            Assert.That(
                (result as RepositoryTennantFilterDecorator<Image>)._decorated,
                Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
        }
    }
}
施飞雨
2023-03-14

存储库模式是一种抽象。它的目的是降低复杂性,并使代码的其余部分持续无知。额外的好处是,它允许你编写单元测试,而不是集成测试。

问题在于,许多开发人员无法理解模式的目的,并创建了将持久性特定信息泄露给调用方的存储库(通常通过公开IQueryable)。

异常编码

使用存储库并不意味着能够切换持久性技术(例如,改变数据库或使用web服务等)。它是关于将业务逻辑从持久性中分离出来,以降低复杂性和耦合性。

单元测试与集成测试

您不为存储库编写单元测试。时期

但是通过引入存储库(或持久性和业务之间的任何其他抽象层),您可以为业务逻辑编写单元测试。i、 e.您不必担心由于数据库配置错误而导致测试失败。

至于查询。如果您使用LINQ,您还必须确保您的查询正常工作,就像处理存储库一样。这是使用集成测试完成的。

不同的是,如果您没有将您的业务与LINQ语句混合在一起,那么您可以100%确定是您的持久性代码失败了,而不是其他原因。

如果你分析你的测试,你也会发现如果你没有混合的关注点(即LINQ商业逻辑),它们会干净得多

存储库示例

大多数例子都是胡说八道。这是非常正确的。然而,如果你谷歌任何设计模式,你会发现很多蹩脚的例子。这不是避免使用模式的理由。

构建正确的存储库实现非常容易。实际上,您只需要遵循一条规则:

不要在存储库类中添加任何东西,直到您需要它的那一刻

许多编码人员很懒,试图制作一个通用的存储库,并使用一个带有许多他们可能需要的方法的基类。YAGNI。你写一次存储库类,只要应用程序存在(可能是几年)就保留它。为什么要偷懒来搞砸它。保持它干净,没有任何基类继承。这将使它更容易阅读和维护。

(上面的陈述是一个指导方针,而不是法律。基类可以很好地被激励。在添加它之前请三思而后行,这样您就可以出于正确的原因添加它)

结论:

如果您不介意在业务代码中包含LINQ语句,也不关心单元测试,我认为没有理由不直接使用实体框架

使现代化

我在博客中介绍了存储库模式和“抽象”的真正含义:http://blog.gauffin.org/2013/01/repository-pattern-done-right/

更新2

对于具有20个字段的单个实体类型,您将如何设计查询方法以支持任何排列组合?您不希望仅通过名称限制搜索,那么使用导航属性进行搜索,列出带有特定价格代码的项目的所有订单,3级导航属性搜索。IQueryable发明的全部原因是能够根据数据库组合搜索。理论上看起来一切都很好,但用户的需求高于理论。

同样:一个有20个字段的实体模型不正确。这是一个上帝实体。分解它。

我并不是说IQueryable不是为查询而生的。我是说它不适合像存储库模式这样的抽象层,因为它是泄漏的。没有100%完整的LINQ To Sql提供程序(如EF)。

它们都有特定于实现的东西,比如如何使用急切/懒惰加载,或者如何执行SQL“IN”语句。在存储库中公开< code>IQueryable会迫使用户知道所有这些事情。因此,抽象数据源的整个尝试是彻底的失败。与直接使用或/M相比,您只是增加了复杂性,而没有获得任何好处。

要么正确实现Repository模式,要么根本不使用它。

(如果你真的想处理大型实体,你可以将Repository模式和Specification模式结合起来。这给了你一个完整的抽象,也是可测试的。)

盖玉石
2023-03-14

我已经走了很多路,并在不同的项目上创建了许多存储库的实现,并且......我已经把毛巾扔进去并放弃了它,这就是原因。

异常编码

您是否为您的数据库从一种技术转换到另一种技术的1%的可能性编写代码?如果你在考虑你的企业未来的状态,并且说是的,那么a)他们必须有很多钱来支付向另一种DB技术的迁移,或者b)你选择DB技术是为了好玩,或者c)你决定使用的第一种技术出了严重的问题。

为什么要抛弃丰富的 LINQ 语法?

LINQ和EF的开发是为了让你可以用它做一些整洁的事情来读取和遍历对象图。创建和维护一个可以给你同样灵活性的存储库是一项艰巨的任务。根据我的经验,每当我创建一个存储库时,我总是有业务逻辑泄漏到存储库层,以提高查询的性能和/或减少数据库的命中视频数。

我不想为我必须编写的查询的每个排列创建一个方法。我还可以编写存储过程。我不想GetOrderGetOrderWithOrderItemSetOrderWithOrderItemWithOrderActivityget OrderByUserId等等……我只想获取主实体,遍历并包含对象图,如我所愿。

大多数储存库的例子都是胡扯

除非你正在开发一些非常简单的东西,比如博客或其他东西,否则你的查询永远不会像你在互联网上找到的90%的关于存储库模式的例子那样简单。我怎么强调都不为过!这是一个人必须在泥泞中爬行才能弄明白的事情。总会有一个查询破坏了您创建的经过完美思考的存储库/解决方案,直到那时,您才会重新审视自己,技术债务/侵蚀才开始。

兄弟,不要对我进行单元测试

但是如果我没有存储库,那么单元测试怎么办呢?我将如何嘲笑?简单,你不会。让我们从两个角度来看:

没有存储库 - 您可以使用 IDbContext 或其他一些技巧来模拟 DbContext,但您实际上是在对 LINQ to Objects 而不是 LINQ to Entities 进行单元测试,因为查询是在运行时确定的......好吧,那不好!因此,现在由集成测试来涵盖这一点。

使用存储库-您现在可以模拟您的存储库并对介于两者之间的层进行单元测试。很棒,对吧?嗯,不是真的…在上述情况下,您必须将逻辑泄漏到存储库层以使查询性能更高和/或对数据库的命中更少,您的单元测试如何涵盖这一点?它现在在回购层,您不想测试IQueryable

2美分

在普通存储过程(批量插入、批量删除、CTE 等)上使用 EF 时,我们已经丢失了许多功能和语法,但我也使用 C# 编写代码,因此我不必键入二进制。我们使用 EF,因此我们可以使用不同的提供程序,并在许多事情中以很好的相关方式处理对象图。某些抽象是有用的,有些则没有。

 类似资料:
  • 我正在尝试学习存储库模式,似乎有点困惑,当我急于加载关系并将db逻辑排除在控制器之外时,如何使用此存储库模式。 快速概述我的存储库/应用程序结构。 示例ProductInterface。php 示例类别接口。php 好的,最简单的部分是使用DI向控制器注入模型依赖关系。 列出与相关产品的所有类别更加困难,因为我不再使用雄辩的模型。我正在使用一个界面,它没有暴露所有雄辩的方法。 如果我没有在我的El

  • 我试图使用Spring JDBC模板调用postgres数据库中的存储过程。通过yaml配置文件完成连接,以指示要使用的当前架构: jdbc:postgresql://localhost:5455/userdb?currentSchema=customschema 直接从jdbc模板完成的每个查询都使用了正确的当前模式“自定义模式”。但是,只有从SimpleJdbcCall执行以调用存储过程是在p

  • 我正在尝试实现一个简单的REST服务,该服务基于具有Spring启动和Spring数据Rest的JPA存储库。(请参阅此教程)如果将以下代码与 gradle 一起使用,则运行良好: 为了让事情变得更简单,我使用Spring boot CLI(“Spring run”命令)尝试了相同的代码。 不幸的是,这似乎不起作用@RepositoryRestResource似乎无法像@RestControlle

  • 问题内容: 我的Maven配置有一个小问题。此处的所有其他问题和答案都不能解决我的问题,因此,我开始了一个新问题。 我的问题是,我的Maven没有使用本地存储库。它总是从远程存储库中获取工件。 当下载工件或构建项目时,它将安装在本地存储库中,因此路径正确。 问题是:当我构建一个SNAPSHOT项目时,它仅安装在本地存储库中(应该是这样,不想每次都将其发布在我的关系上)。当我在pom.xml中构建另

  • 我的Maven配置有一个小问题。这里所有其他的问题和答案都没有解决我的问题,所以我要开始一个新的问题。 我的问题是,我的Maven没有使用本地存储库。它总是从远程存储库中获取工件。 当下载工件或构建项目时,它被安装在本地存储库中,因此路径是正确的。 问题是:当我构建一个快照项目时,它只安装在本地存储库中(应该是这样的,不要每次都在我的nexus上发布)。当我构建另一个项目时,在pom.xml中有前