4

我们使用 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(m.Details 中的 var d)

此错误永远不会消失,即缓存中的某些数据已损坏

带有以下堆栈:

  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(var d in m.Details)

如果我停止使用并行请求访问该站点,此错误会在一段时间后消失

有以下堆栈

   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 数据对象带有它们的上下文。 这可能是唯一一个我还没有答案的问题。

关于它为什么坏的理论

看起来将 LINQ-SQL 对象存储在 http 缓存中带有数据上下文。当此上下文稍后被多个线程用于访问子对象时,会出现某种类型的并发问题,表现为临时连接问题或子对象的完全数据损坏。由于无法从 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

4

1 回答 1

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

var master = db.Masters.ToList();

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

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

于 2015-09-21T16:34:07.137 回答