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

每个web请求一个DbContext...为什么?

冯招
2023-03-14

为什么这是一个好主意呢?您通过使用这种方法获得了什么好处?在某些情况下,这会是一个好主意吗?在每个存储库方法调用实例化dbcontext时,您是否可以使用这种技术做一些不能做的事情?

共有1个答案

易星宇
2023-03-14

注意:这个答案讨论了实体框架的dbcontext,但是它适用于任何类型的工作单元实现,比如LINQ to SQL的datacontext和NHibernate的isession

让我们从回显Ian开始:为整个应用程序设置一个dbcontext是个坏主意。唯一有意义的情况是,当您有一个单线程应用程序和一个仅由该应用程序实例使用的数据库时。dbcontext不是线程安全的,而且由于dbcontext缓存数据,它很快就过时了。当多个用户/应用程序同时在该数据库上工作时,这会给您带来各种各样的麻烦(当然,这是非常常见的)。但是我希望您已经知道这一点,并且想知道为什么不将dbcontext的新实例(例如,使用临时的生活方式)注入到任何需要它的人中。(有关为什么单个dbcontext-甚至每个线程的上下文-不好的更多信息,请阅读以下答案)。

让我首先说明,将dbcontext注册为transit可以起作用,但通常您希望在特定范围内拥有这样一个工作单元的单个实例。在web应用程序中,可以在web请求的边界上定义这样一个范围;因此,每个Web请求的生活方式。这允许您让一整套对象在相同的上下文中操作。换句话说,它们在同一个业务事务中运行。

如果您的目标不是让一组操作在相同的上下文中运行,在这种情况下,短暂的生活方式是好的,但有几件事要注意:

  • 因为每个对象都有自己的实例,所以每个更改系统状态的类都需要调用_context.savechanges()(否则更改将丢失)。这会使代码复杂化,并向代码添加第二个职责(控制上下文的职责),并且违反了单一职责原则。
  • 您需要确保实体[由dbcontext]加载和保存]永远不会离开这样一个类的作用域,因为它们不能在另一个类的上下文实例中使用。这会使代码变得非常复杂,因为当您需要这些实体时,需要再次按id加载它们,这也可能导致性能问题。
  • 因为dbcontext实现了idisposable,所以您可能仍然希望处理所有创建的实例。如果你想这样做,你基本上有两个选择。在调用context.saveChanges()之后,您需要在相同的方法中处理它们,但是在这种情况下,业务逻辑将拥有它从外部传递的对象的所有权。第二个选项是在Http请求的边界上释放所有创建的实例,但是在这种情况下,您仍然需要某种范围来让容器知道何时需要释放这些实例。

另一种选择是根本不注入dbcontext。相反,您可以插入能够创建新实例的DBContextFactory(我过去使用过这种方法)。这样,业务逻辑显式地控制上下文。如果看起来像这样:

public void SomeOperation()
{
    using (var context = this.contextFactory.CreateNew())
    {
        var entities = this.otherDependency.Operate(
            context, "some value");

        context.Entities.InsertOnSubmit(entities);

        context.SaveChanges();
    }
}

缺点是,您必须在每个方法之间传递dbcontext(称为方法注入)。请注意,从某种意义上说,这个解决方案与“作用域”方法相同,但是现在作用域是在应用程序代码本身中控制的(并且可能会重复多次)。应用程序负责创建和处理工作单元。由于dbcontext是在构造依赖关系图之后创建的,因此构造函数注入是不可能的,当您需要将上下文从一个类传递到另一个类时,您需要推迟到方法注入。

方法注入并不是那么糟糕,但是当业务逻辑变得更加复杂,并且涉及到更多的类时,您将不得不将它从一个方法传递到另一个方法,从一个类传递到另一个类,这会使代码变得非常复杂(我以前见过这种情况)。对于一个简单的应用程序,这种方法会做得很好。

由于缺点,这种工厂方法对于更大的系统是有用的,另一种方法是让容器或基础结构代码/组合根管理工作单元的方法。这就是你的问题所涉及的风格。

通过让容器和/或基础结构来处理这个问题,您的应用程序代码不会因为创建、(可选地)提交和处理UoW实例而受到污染,这将保持业务逻辑的简单和干净(只负责一件事)。这种方法有一些困难。例如,您是否提交和处置实例?

处理一个工作单元可以在web请求结束时完成。然而,许多人错误地认为这也是提交工作单元的地方。但是,在应用程序的这一点上,您根本无法确定工作单元实际上应该提交。例如,如果业务层代码抛出了一个在调用堆栈的更高位置捕获的异常,那么您肯定不想提交。

真正的解决方案还是显式地管理某种范围,但这次是在复合根中进行。抽象命令/处理程序模式背后的所有业务逻辑,您将能够编写一个装饰器,该装饰器可以包装在允许这样做的每个命令处理程序周围。示例:

class TransactionalCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand>
{
    readonly DbContext context;
    readonly ICommandHandler<TCommand> decorated;

    public TransactionCommandHandlerDecorator(
        DbContext context,
        ICommandHandler<TCommand> decorated)
    {
        this.context = context;
        this.decorated = decorated;
    }

    public void Handle(TCommand command)
    {
        this.decorated.Handle(command);

        context.SaveChanges();
    } 
}
 类似资料:
  • 为什么首先这是一个好主意?通过使用这种方法,您获得了哪些优势?在某些情况下,这是一个好主意吗?在每个存储库方法调用实例化s时,使用此技术是否可以执行一些不能执行的操作?

  • 问题内容: 我们有一个weblogic批处理应用程序,它可以同时处理来自使用者的多个请求。我们使用log4j记录目的。现在,我们登录到单个日志文件以处理多个请求。调试给定请求的问题变得很麻烦,因为所有请求都将日志记录在一个文件中。 因此,计划是每个请求只有一个日志文件。使用者发送一个请求ID,必须对其进行处理。现在,实际上可能有多个使用者将请求ID发送到我们的应用程序。因此,问题是如何根据请求隔离

  • 在上面的代码示例中,试图为每个请求生成唯一的txn-id。但是,观察到它正在重复使用相同的数字导致重复。我还尝试使用以下方法根据当前时间生成,当在1秒内触发超过1个请求时,它仍然会导致重复。 任何替代解决方案来为每个请求生成唯一ID,而不管并发情况如何? 提前感谢。

  • 问题内容: 我刚刚启动了一个简单的Java测试项目,该项目使用Hibernate管理某些实体,并提供REST接口来操纵这些对象并提供一些其他业务逻辑。REST接口是使用RESTEasy和Jetty创建的。 到目前为止,一切工作正常,但是我感觉我实际上写了太多样板代码。由于我在这些Java框架中没有太多经验,所以我只是想知道是否有人可以给我提示如何改善这种情况。 每个请求创建hibernate会话

  • 问题内容: 我已经阅读了有关node.js和其他服务器(例如Apache)的信息,其中线程有所不同。我根本不了解线程的含义。 如果我有一个运行SQL的网页来访问数据库,请在一个服务器端页面中说三个不同的数据库,这对node.js中的线程意味着什么?阿帕奇?“线程”在这里是什么意思? 或者作为我看到的文章,“启动一个新线程来处理每个请求”。 说Apache为每个请求生成一个线程,但node.js却没

  • 我尝试分析一个gRPC java服务器。我主要看到下面的一组线程池。 null