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 />
}
それは完全に正常に機能し、データを正しくキャッシュします。ただし、キャッシュがクリアされた後に複数のリクエストが Web サイトにヒットしようとすると失敗します - 基本的に多くの (50) 並列スレッドでサイトにヒットし、web.config に触れる JMeter を使用してこれをテストしています - すぐに見始めます次の 2 つのエラーのいずれか:
インデックスが配列の範囲外だった
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& 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 />
}
エンティティ フレームワークへの切り替え
これには、特定の数の並列リクエスト(コアi7で50以上)で「オープン接続」の問題がまだあるようです(時々-linq-sqlよりもはるかに優れています)-私が述べたように、しばらくすると消えますが、私はしていませんまだデータ破損が見られます。
これが唯一の実行可能なパスであるように思われるため、完全に 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=共有
テストするには、サイトを開始してテストを実行します - 次に、サイトの web.config に触れます