NInject 3 と (Fluent) NHibernate 3.2 を使用する ASP.Net 4 / MVC 3 ハイブリッド Web アプリケーションがあります。DB は SQL Server 2008 R2 です。サーバーは 6 コア 28 GB Windows 2008 64 ビット サーバーです。
当社の顧客は最近、スパイダー ツールを使用してサイトのテストを開始しました。スパイダーによって生成された負荷がサイトに発生するとすぐに、ログが例外でいっぱいになり始めます。
次のいくつかを含む、NHibernate からのさまざまなエラーが表示されます。
NHibernate.TransactionException: コミットが SQL 例外で失敗しました ---> System.Data.SqlClient.SqlException: このトランザクションで処理中の保留中の要求があるため、トランザクション操作を実行できません。
System.Data.SqlClient.SqlException (0x80131904): サーバーはトランザクションを再開できませんでした。説明:410000050f. このセッションでアクティブなトランザクションは、別のセッションによってコミットまたは中止されました。
System.NullReferenceException: オブジェクト参照がオブジェクトのインスタンスに設定されていません。System.Data.SqlClient.SqlInternalTransaction.GetServerTransactionLevel() で...
NHibernate.Exceptions.GenericADOException: ネイティブの一括操作クエリを実行できませんでした:exec [Stats.InsertListingStatsList] @ListingStats =:ListingStats[SQL: exec [Stats.InsertListingStatsList] @ListingStats =@p0] ---> System.Data.SqlClient. SqlException: 有効なトランザクション記述子が必要なため、新しい要求を開始できません。
例を 4 つ挙げます。どれも似たような趣を持っています - それらはすべて、NHibernate の基盤としての ADO.Net によるトランザクションの管理に関連しているようです。
ここで、NH 実装の詳細をいくつか示します。
- SessionFactory は静的です。
- SessionFactory は AdoNetTransactionFactory を使用します。
- ISession はリクエスト スコープ内にあり、HttpContext.Items コレクションに格納されます。
- リポジトリもリクエスト スコープにあります。
- 現在、config.CurrentSessionContext(); を使用しています。
- 汎用リポジトリへの各呼び出しはトランザクションを使用します
リポジトリからの 2 つの方法を次に示します。
public T GetById<T>(int id)
{
using (var t = Session.BeginTransaction())
{
var entity = Session.Get<T>(id);
t.Commit();
return entity;
}
}
public void Add<T>(T entity)
{
using (var t = Session.BeginTransaction())
{
Session.Save(entity);
t.Commit();
}
}
私の質問は簡単です。何がうまくいかないのですか? トランザクション間、またはドメインがドメインを脱水/水和するときに、ドメインが開始するさまざまなデータ関連の操作間で、これらの明らかな競合を引き起こしているのは何ですか?
更新: 完全な構成は次のとおりです。
public FluentConfiguration BuildConfiguration(string connectionString)
{
var sqlConfig = MsSqlConfiguration.MsSql2008.ConnectionString(connectionString).AdoNetBatchSize(30);
var config = Fluently.Configure().Database(sqlConfig);
var entityMapping = AutoMap.AssemblyOf<User>(new AutomappingConfiguration())
.UseOverridesFromAssemblyOf<UserMappingOverride>()
.AddMappingsFromAssemblyOf<TableNamingConvention>()
.Conventions.AddFromAssemblyOf<TableNamingConvention>();
var cqrsMapping = AutoMap.AssemblyOf<AdvertView>(new QueryAutomappingConfiguration())
.UseOverridesFromAssemblyOf<AdvertViewMappingOverride>();
config.Mappings(c => c.AutoMappings.Add(entityMapping));
config.Mappings(c => c.AutoMappings.Add(cqrsMapping));
config.Mappings(c => c.HbmMappings.AddFromAssemblyOf<AdvertView>());
config.ExposeConfiguration(c => c.SetProperty(Environment.TransactionStrategy, typeof(AdoNetTransactionFactory).FullName));
config.CurrentSessionContext<WebSessionContext>();
return config;
}
皆さんとギャルのためのより多くのコード。これは、IoC コンテナー構成の関連セクションです。
var domainEntityBootstrapper = new DomainEntitySessionBootStrapper("Domain", "NHibernate.ISession.Domain", _enableLucine, HttpContextItemsProvider);
Bind<ISessionFactory>().ToMethod(domainEntityBootstrapper.CreateSessionFactory).InSingletonScope().Named(domainEntityBootstrapper.Name);
Bind<ISession>().ToMethod(domainEntityBootstrapper.GetSession).InRequestScope();
var queryBootstrapper = new QueryEntitySessionBootStrapper("Query", "NHibernate.ISession.Query", HttpContextItemsProvider);
Bind<ISessionFactory>().ToMethod(queryBootstrapper.CreateSessionFactory).InSingletonScope().Named(queryBootstrapper.Name);
Bind<ISession>().ToMethod(queryBootstrapper.GetSession).WhenInjectedInto(typeof (QueryExecutor)).InRequestScope();
これらの SessionBootstrappers の基本クラスの GetSession() メソッドからのコードを次に示します (CreateSessionFactory メソッドは上記の BuildConfiguration メソッドを呼び出し、次に BuildSessionFactory() を呼び出すことに注意してください)。
public virtual ISession GetSession(IContext context)
{
var items = GetHttpContextItems();
var session = default(ISession);
var sessionExists = items.Contains(SessionKey);
if (!sessionExists)
{
session = context.Kernel.Get<ISessionFactory>(Name).OpenSession();
items.Add(SessionKey, session);
}
else
{
session = (ISession)items[SessionKey];
}
return session;
}
// a Func which serves access to the HttpContext.Current.Items collection
private Func<IDictionary> GetHttpContextItems { get; set; }
2 つのセッションを使用することに注意してください。1 つは通常のドメインの脱水/ハイドレーション用で、もう 1 つは CQRS 用です。したがって、コンテナ内のバインディングのペアです。