我们使用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& 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
答案 0 :(得分:0)
LockApp.Models.DBDataContext db = new Models.DBDataContext();
var master = db.Masters.ToList();
您应该在这两个电话之间拨打db.ObjectTrackingEnabled = false
。否则,datacontext将跟踪所有对象,以便可以将更改写回数据库。由于您要缓存这些对象以供多个线程读取,因此您不需要这样做。 (即使在单线程情况下跟踪你赢得的对象也不会变得更加昂贵,所以在其他地方也值得做。)
此外,使用LoadWith
急切地加载您可能想要访问这些缓存实体的任何属性,因此它们都被加载到初始缓存线程上,而不是尝试访问它们的(可能是多个)线程