每个Web请求一个DbContext ...为什么? - c#

我读了很多文章,解释了如何设置Entity Framework的DbContext,以便使用各种DI框架为每个HTTP Web请求创建和使用一个。

为什么这首先是个好主意?通过使用这种方法,您可以获得什么优势?在某些情况下这是个好主意吗?实例化每个存储库方法调用的DbContext时,您是否可以使用此技术做一些事情?

参考方案

注意:此答案是关于实体框架的DbContext,但是
它适用于任何类型的工作单元实施,例如
LINQ to SQL的DataContext和NHibernate的ISession

让我们回首伊恩:让整个应用程序只有一个DbContext是一个坏主意。唯一有意义的情况是当您拥有一个单线程应用程序和一个仅由该单个应用程序实例使用的数据库时。 DbContext不是线程安全的,并且由于DbContext缓存数据,因此很快就会过时。当多个用户/应用程序同时在该数据库上工作时,这将给您带来各种麻烦(这是很常见的)。但我希望您已经知道这一点,并且只想知道为什么不将DbContext的新实例(即具有短暂生活方式)注入需要的任何人。 (有关为何单个DbContext或什至每个线程的上下文不好的原因的更多信息,请阅读this answer)。

首先让我说,将DbContext注册为瞬态可以工作,但是通常您希望在一定范围内拥有此类工作单元的单个实例。在Web应用程序中,在Web请求的边界上定义这样的范围可能是实用的。因此,按网络请求的生活方式。这使您可以让整套对象在同一上下文中操作。换句话说,它们在同一业务交易中运作。

如果您没有目标要在同一上下文中进行一组操作,那么在这种情况下,短暂的生活方式就可以了,但是需要注意以下几点:

由于每个对象都有其自己的实例,因此更改系统状态的每个类都需要调用_context.SaveChanges()(否则更改将丢失)。这可能会使您的代码复杂化,并向代码添加第二个责任(控制上下文的责任),并且违反了Single Responsibility Principle。
您需要确保[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

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

方法注入并没有那么糟糕,但是当业务逻辑变得更加复杂,并且涉及到更多的类时,您将不得不将其从方法传递到方法,并将类传递给类,这会使代码复杂化很多(我已经看到了过去)。对于简单的应用程序,此方法虽然可以。

由于不利因素,这种工厂方法适用于较大的系统,另一种方法可能有用,那就是让容器或基础结构代码/ Composition Root管理工作单元的方法。这是您的问题涉及的样式。

通过让容器和/或基础结构处理此问题,您的应用程序代码不会因必须创建,(可选)提交和处置UoW实例而受到污染,这使业务逻辑变得简单,整洁(仅是单一职责)。这种方法存在一些困难。例如,您是否提交并处置该实例?

可以在Web请求结束时完成工作单元的布置。但是,许多人错误地认为这也是提交工作单元的地方。但是,在应用程序中的那一点上,您根本无法确定应确实落实工作单元。例如如果业务层代码引发了一个在调用堆栈上被捕获的异常,则您绝对不想提交。

真正的解决方案是再次明确管理某种范围,但是这次在“合成根”内部执行。在command / handler pattern之后抽象所有业务逻辑,您将能够编写一个装饰器,该装饰器可以包装在允许执行此操作的每个命令处理程序中。例:

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

这样可以确保您只需要编写一次此基础结构代码。任何固体DI容器都允许您将这样的装饰器配置为以一致的方式包装在所有ICommandHandler<T>实现中。

Math.log无法正常工作-为什么? - java

//以下方法无法正常工作,我不确定为什么。日志结果不正确。 0.5返回日志值-0.6931471805599453,但是我的计算器显示它应该返回-3.3。public static void printCommonLogTable() { double x = 0; while (x <= 10) { System.out.println (x+ &#…

使用Entity Framework Core(2.1)调用标量函数的最佳实践 - c#

我经常需要从Web应用程序(ASP.NET Core / EF Core)中调用在SQL Server上定义的标量函数。由于这些函数只是简单的辅助函数,因此我也使用了许多辅助函数,因此我使用了通用模式来调用这些标量函数-借助EF Core 2.1可用的新查询类型。由于我是EF Core的新手,所以我的问题是这种模式是否会引起问题,并且/或者是否存在调用标量函…

在Laravel 4中JQuery没有返回数据..为什么? - javascript

我的项目是电子商务....在布局product.blade.php文件中{{ HTML::script('js/jquery.js') }} <script type="text/javascript"> $("#frm").submit(function(e){ $('#re…

Play Framework 2中的系统类加载器 - java

我使用的是Play 2.2.2,我有一个外部jar,它试图从同一jar的根目录加载XML资源。它使用System.class.getClassLoader().getResource("/Blabla.xml")这样做。这失败了,因为显然Play拥有一个奇怪的类加载器层次结构:ReloadableClassLoader和几个父级。该层次结…

如何以编程方式将ListView滚动到最后一个元素-Compact Framework - c#

我正在使用Windows Mobile 6.1上的3.5 Compact Framework开发应用程序。我有一个ListView,添加项目时想自动滚动此列表。我能怎么做? 参考方案 listView.EnsureVisible(listView.Items.Count - 1);