缓存LINQ-SQL对象和DataContext线程的安全性

时间:2013-07-26 15:57:12

标签: asp.net .net entity-framework linq-to-sql thread-safety

我们使用LINQ-SQL查询数据库,然后将生成的主表对象存储在HTTP缓存中。 稍后,使用延迟加载,主对象用于查询其子对象。以下是相关的代码片段 - 我在一个新的概念验证应用程序中重新创建了场景:

        if (HttpRuntime.Cache["c"] == null)
        {
            LockApp.Models.DBDataContext db = new Models.DBDataContext();

            var master = db.Masters.ToList();
            HttpRuntime.Cache.Add("c", master,
                    null, DateTime.Now.AddMonths(1), 
                    TimeSpan.Zero, CacheItemPriority.Normal, null);

        }

        ViewBag.Data = (List<LockApp.Models.Master>)HttpRuntime.Cache["c"];

这是在主要和细节对象上迭代的剃刀视图:

    @foreach(var m in ViewBag.Data){
        @m.Id<nbsp></nbsp>
        foreach(var d in m.Details){
            @d.Id<nbsp></nbsp>
        }
        <br />
    }

它完全正常并正确缓存数据。但是,当清除缓存后有多个请求尝试访问网站时失败 - 我正在使用JMeter测试它,基本上用很多(50)并行线程命中网站,然后触摸web.config - 我立即开始看到以下两个错误之一:

索引超出数组范围

在foreach(var d in m.Details)

此错误永远不会消失,即某些数据在缓存中被破坏

以下堆栈:

  System.Collections.Generic.List`1.Add(T item) +34
   System.Data.Linq.SqlClient.SqlConnectionManager.UseConnection(IConnectionUser user) +305
   System.Data.Linq.SqlClient.SqlProvider.Execute(Expression query, QueryInfo queryInfo, IObjectReaderFactory factory, Object[] parentArgs, Object[] userArgs, ICompiledSubQuery[] subQueries, Object lastResult) +59
   System.Data.Linq.SqlClient.SqlProvider.ExecuteAll(Expression query, QueryInfo[] queryInfos, IObjectReaderFactory factory, Object[] userArguments, ICompiledSubQuery[] subQueries) +118
   System.Data.Linq.SqlClient.CompiledQuery.Execute(IProvider provider, Object[] arguments) +99
   System.Data.Linq.DeferredSourceFactory`1.ExecuteKeyQuery(Object[] keyValues) +402
   System.Data.Linq.DeferredSourceFactory`1.Execute(Object instance) +888
   System.Data.Linq.DeferredSource.GetEnumerator() +51
   System.Data.Linq.EntitySet`1.Load() +107
   System.Data.Linq.EntitySet`1.GetEnumerator() +13
   System.Data.Linq.EntitySet`1.System.Collections.IEnumerable.GetEnumerator() +4
   ASP._Page_Views_Home_Index_cshtml.Execute() in c:\Users\prc0092\Documents\Visual Studio 2012\Projects\LockApp\LockApp\Views\Home\Index.cshtml:16

或此错误

ExecuteReader需要一个开放且可用的连接。连接的当前状态已打开。

在同一行foreach(m.Details中的var d)

如果我停止使用并行请求点击网站

,此错误会在一段时间后消失

以下堆栈

   System.Data.SqlClient.SqlConnection.GetOpenConnection(String method) +5316460
   System.Data.SqlClient.SqlConnection.ValidateConnectionForExecute(String method, SqlCommand command) +7
   System.Data.SqlClient.SqlCommand.ValidateCommand(String method, Boolean async) +155
   System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task&amp; task, Boolean asyncWrite) +82
   System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method) +53
   System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method) +134
   System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior) +41
   System.Data.Common.DbCommand.ExecuteReader() +12
   System.Data.Linq.SqlClient.SqlProvider.Execute(Expression query, QueryInfo queryInfo, IObjectReaderFactory factory, Object[] parentArgs, Object[] userArgs, ICompiledSubQuery[] subQueries, Object lastResult) +1306
   System.Data.Linq.SqlClient.SqlProvider.ExecuteAll(Expression query, QueryInfo[] queryInfos, IObjectReaderFactory factory, Object[] userArguments, ICompiledSubQuery[] subQueries) +118
   System.Data.Linq.SqlClient.CompiledQuery.Execute(IProvider provider, Object[] arguments) +99
   System.Data.Linq.DeferredSourceFactory`1.ExecuteKeyQuery(Object[] keyValues) +402
   System.Data.Linq.DeferredSourceFactory`1.Execute(Object instance) +888
   System.Data.Linq.DeferredSource.GetEnumerator() +51
   System.Data.Linq.EntitySet`1.Load() +107
   System.Data.Linq.EntitySet`1.GetEnumerator() +13
   System.Data.Linq.EntitySet`1.System.Collections.IEnumerable.GetEnumerator() +4
   ASP._Page_Views_Home_Index_cshtml.Execute() in c:\Users\prc0092\Documents\Visual Studio 2012\Projects\LockApp\LockApp\Views\Home\Index.cshtml:16

我试过的不同的事情

双重锁定

没有帮助

    private static object ThisLock = new object();

    public ActionResult Index()
    {

        if (HttpRuntime.Cache["c"] == null)
        {
            lock (ThisLock)
            {
                if (HttpRuntime.Cache["c"] == null)
                {

预先加载子数据

工作,但需要不断维护,因为不是所有的孩子都应该预先加载,另外请参阅下一个注释

DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Master>(b => b.Details);
db.LoadOptions = dlo;

在尝试访问其子级时锁定主对象

同样,需要维护,因为需要找到访问子项的所有初始位置 - 我们正在努力解决这个问题,因为网站有不同的入口路径

    @foreach(var m in ViewBag.Data){
        @m.Id<nbsp></nbsp>
        lock (m){
            foreach(var d in m.Details){
                @d.Id<nbsp></nbsp>
            }
        }
        <br />
    }

切换到实体框架

这似乎仍然(有时 - 比linq-sql好得多)在一定数量的并行请求(核心i7上50+)的“开放连接”问题 - 它确实在我提到的一段时间后消失了我还没有看到数据损坏。

我们最终可能会完全切换到EF,因为这似乎是唯一可行的路径 - 假设数据损坏没有显示 - 这将在我的实际项目中进行测试。

我并不乐观,因为EF数据上下文也不是线程安全的,我认为EF数据对象带有它们的上下文。 这可能是我还没有回答的唯一问题。

关于它为何被打破的理论

看起来在http缓存中存储LINQ-SQL对象会携带数据上下文。当此上下文稍后被多个线程用于访问子级时,存在某种类型的并发问题,该问题在临时连接问题或子对象的完全数据损坏中表现出来。由于没有办法断开/重新连接LINQ对象的上下文,看起来唯一的建议是不要缓存需要延迟加载孩子的LINQ对象 - 我做的大量谷歌搜索似乎没有给你这个建议,实际上有时它是相反的。

我上传了完整的项目(适用于Visual Studio 2012和SQL Server 2012)

https://docs.google.com/file/d/0B8CQRA9dD8POb3U5RGtCV3BMeU0/edit?usp=sharing 和一个简单的JMeter脚本,它将使用并行请求命中本地计算机: https://docs.google.com/file/d/0B8CQRA9dD8POd1VYdGRDMEFQbEU/edit?usp=sharing

进行测试,启动网站并运行测试 - 然后触摸网站上的web.config

1 个答案:

答案 0 :(得分:0)

LockApp.Models.DBDataContext db = new Models.DBDataContext();

var master = db.Masters.ToList();

您应该在这两个电话之间拨打db.ObjectTrackingEnabled = false。否则,datacontext将跟踪所有对象,以便可以将更改写回数据库。由于您要缓存这些对象以供多个线程读取,因此您不需要这样做。 (即使在单线程情况下跟踪你赢得的对象也不会变得更加昂贵,所以在其他地方也值得做。)

此外,使用LoadWith急切地加载您可能想要访问这些缓存实体的任何属性,因此它们都被加载到初始缓存线程上,而不是尝试访问它们的(可能是多个)线程