5

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 用です。したがって、コンテナ内のバインディングのペアです。

4

2 に答える 2

1

エラー メッセージは、トランザクションを正しく管理していないことを示しています。根本的な原因は、私の意見では非常に貧弱な設計であるリポジトリ メソッドでトランザクションを処理していることだと思います。リポジトリには ISession がコンストラクターに注入されている必要があり、コントローラーには依存するすべてのリポジトリーがコンストラクターに注入されている必要があります。Ninject を使用すると、これらすべてを簡単に結び付けることができます。このアプローチでは、リクエストごとのトランザクションを使用するか、アクションメソッドでトランザクションを管理できます。

NinjectWebCommon で Ninject を使用して NHibernate をセットアップする方法を次に示します。問題の根本的な原因は、リクエスト スコープで ISession をバインドし、それを HttpContext に格納していることが原因である可能性がありますが、これは不要です。また、ドメインとクエリの 2 つのバインディング セットがある理由もわかりません。

    private static void RegisterServices(IKernel kernel)
    {
        kernel.Bind<ISessionFactory>().ToProvider(new SessionFactoryProvider()).InSingletonScope();
        kernel.Bind<ISession>().ToProvider(new SessionProvider()).InRequestScope();
    }

    private class SessionFactoryProvider : Provider<ISessionFactory>
    {
        protected override ISessionFactory CreateInstance(IContext context)
        {
           // create and configure the session factory
           // I have a utility class to do this so the code isn't shown
           return nhibernateHelper.BuildSessionFactory();
        }
    }

    private class SessionProvider : Provider<ISession>
    {
        protected override ISession CreateInstance(IContext context)
        {
            var sessionFactory = context.Kernel.Get<ISessionFactory>();
            var session = sessionFactory.OpenSession();
            session.FlushMode = FlushMode.Commit;
            return session;
        }
    }

トランザクションを使用したコントローラー アクションの例。リポジトリ外でトランザクションを管理することは、いくつかの理由で重要です。

  • 複数のリポジトリがトランザクションに参加できるようにする
  • コントローラーがトランザクション境界 (作業単位) を設定できるようにします。
  • トランザクションでの遅延ロードの発生を許可します
  • 第 2 レベルのキャッシュが使用されている場合、読み取り操作にはトランザクションが必要です。キャッシングを使わなくてもベストプラクティスだと思います

    public ActionResult EditDocuments(int id, string name)
    {
        using (var txn = _session.BeginTransaction())
        {
            var summary = _characterizationRepository
                .GetCharacterization(id)
                .AsCharacterizationSummaryView()
                .ToFutureValue();
    
            var documents = _characterizationRepository
                .GetCharacterization(id)
                .SelectMany(c => c.Documents)
                .OrderBy(d => d.FileName)
                .AsDocumentSelectView(true)
                .ToFuture();
    
            if (summary.Value == null)
            {
                throw new NotFoundException(_characterizationRepository.ManualId, "Characterization", id);
            }
    
            CheckSlug(name, summary.Value.Title);
    
            var model = new DocumentSectionEditView()
                {
                    CharacterizationSummary = summary.Value,
                    Documents = documents.ToArray()
                };
    
            txn.Commit();
            return View(model);
        }
    }
    
于 2012-12-17T20:57:54.060 に答える
0

間違ったコンテキスト マネージャーを使用しているようです。WebSessionContext を使用しているかどうかを確認してください。このコンテキスト マネージャーは、スレッドではなく、現在の呼び出しの httpcontext にセッションをバインドします。ThreadStaticSessionContext を使用している場合、負荷 (スパイダー) の下で何が起こるか、セッションは別の「呼び出し」に「ジャンプ」します。

于 2012-12-17T16:42:21.790 に答える