【问题标题】:Asp.net web api + entity framework: multiple requests cause data conflictAsp.net web api + 实体框架:多次请求导致数据冲突
【发布时间】:2013-12-22 13:23:30
【问题描述】:

我正在使用 EF6.02 和 Web API 2 使用 VS2013 开发应用程序。我正在使用 ASP.NET SPA 模板,并针对由 sql 服务器支持的实体框架数据源创建 RESTful api。 (在开发中,它驻留在 SQL Server 本地实例上。)

到目前为止,我有两种 API 方法(一种只是读取数据,一种是写入数据),我正在通过在 javascript 中调用它们来测试它们。当我只在脚本中调用一个方法时,任何一个都可以完美运行。但是,如果我在脚本中调用两者(不等待任何一个回调触发),我会在调试器中得到不好的结果和不同的异常。一些例外情况表明无法完成保存,因为有待处理的事务。另一个例外说明了与其他线程的冲突。有时,在尝试读取结果集时,读取操作会失败并出现空指针异常。

“不允许新事务,因为会话中还有其他线程在运行。”

这让我怀疑我是否正确地为每个请求获取了一个新的 DBContext。我的代码如下:

    static Startup()
    {
        context = new Data.SqlServer.AppDbContext();
  ...
    }

然后每当实例化一个工作单元时,我都会访问 Startup.context。

我尝试实现工作单元模式,每个请求共享一个 UOW 对象,该对象具有一个 DBContext 对象。

我的问题:我是否有额外的责任来确保网络请求能够“很好地配合”彼此?我希望这是其他人已经处理过的问题。也许我看到的错误是合法的,因为如果一个用户的数据被触摸,它暂时处于无效状态,如果其他请求在那个时候进入,它们确实会失败(我应该编码预期这些失败)。我猜即使每个请求都有自己的 DBContext,它们仍然共享相同的底层 SQL 数据源,所以这可能会导致问题。

我可以尝试整理一个测试用例,但我会得到不同的行为,具体取决于我放置断点的位置以及我在它们上花费的时间,向我重申这是与时间相关的。

感谢您的任何帮助或建议... -本

【问题讨论】:

    标签: asp.net sql asp.net-mvc entity-framework


    【解决方案1】:

    你的问题是你在哪里设置你的上下文。 Startup 方法适用于整个应用程序启动时,因此发出的任何请求都将使用相同的上下文。这不是按请求设置,而是按应用程序设置。至于为什么会出现错误,EntityFramework 不是线程安全的。由于 IIS 产生了许多线程来处理并发请求,因此您的单个 context 正在跨多个线程使用。

    至于解决办法,你可以看看

    -依赖注入框架(如NinjectUnity

    -在你的 UnitOfWork 类中放置一条 using 语句

    using(var context = new Data.SqlServer.AppDbContext()){//do stuff}
    

    -或者,我看到人们创建一个类的实例,该类获取该请求的上下文并将其存储在 HttpContext.Cache[] 元素中(使用唯一名称,以便您可以轻松地在另一个类中检索它),使得这样您就可以为相同的请求重用相同的上下文。像这样的:

    public AppDbContext GetDbContext()
        {
            var httpContext = HttpContext.Current;
            if (httpContext == null) return new AppDbContext();
            const string contextTypeKey = "AppDbContext";
            if (httpContext.Items[contextTypeKey] == null)
            {
                httpContext.Items.Add(contextTypeKey, new AppDbContext());
            }
            return httpContext.Items[contextTypeKey] as AppDbContext;
        }
    

    如需使用上述方法,只需简单调用var context = GetDbContext();

    注意

    上面的方法我们都有,但这是专门针对第三种方法的。有两个警告似乎效果很好。首先,不要在 using 语句中使用它,因为它在请求范围内对任何其他类都不可用(您将其释放)。其次,确保您拨打了Application_EndRequest 的电话,该电话确实会处理它。我们看到这些小虫子在请求结束后在内存中徘徊,导致内存使用量激增。

    【讨论】:

    • 非常感谢汤米的详细回复。是时候卷起袖子了!
    • 我有另一个想法:在控制器的构造函数中实例化数据上下文而不是“使用”语句是否有意义?
    • @BenjiFB - 当然,这就是选项一发挥作用的地方。您可以使用 Ninject/Unity 将上下文注入您的控制器/工作单元类等。这些也可以配置为每个请求一次,因此如果您愿意,每次都不会注入一个新副本。
    • 嘿@Tommy,我有一个后续问题要问你:我在 Startup 方法中执行 init 的原因是因为我使用了 VS2013 SPA 模板,而这就是他们初始化 UserManager 对象的地方。模板使用了没有 DbContext 的重载,所以我显式地创建了 DbContext 并将其传入(这样我就可以重用它)。但这引出了一个问题:虽然似乎有必要将 DbContext 的范围缩小(针对单个请求而不是应用程序启动),但我们可以将 UserManager 的范围限定为整个应用程序吗?还是模板使用了不好的做法? (下接)
    • 您可以忽略该评论。我认为我的新问题足够不同,因此我将其单独发布:stackoverflow.com/questions/20790990/…
    猜你喜欢
    • 1970-01-01
    • 2020-06-10
    • 1970-01-01
    • 2015-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-25
    相关资源
    最近更新 更多