5

我们有一个使用 NInject 3 和(Fluent)NHibernate 3.2 的 ASP.Net 4 / MVC 3 混合 Web 应用程序。数据库是 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:不允许启动新请求,因为它应该带有有效的事务描述符。

仅举四个例子。它们都具有相似的风格——它们似乎都与作为 NHibernate 基础的 ADO.Net 对事务的管理有关。

现在,我们的 NH 实现的一些细节:

  • SessionFactory 是静态的;
  • SessionFactory 使用 AdoNetTransactionFactory;
  • ISession 在请求范围内,存储在 HttpContext.Items 集合中;
  • 存储库也在请求范围内;
  • 我们现在使用 config.CurrentSessionContext();
  • 对我们的通用存储库的每次调用都使用一个事务

这是我们存储库中的两种方法。

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; }

请注意,我们使用两个会话,一个用于普通域脱水/水化,一个用于 CQRS,因此容器中的绑定对。

4

2 回答 2

1

错误消息表明您没有正确管理事务。我认为根本原因是您正在处理存储库方法中的事务,我认为这是一个非常糟糕的设计。你的存储库应该有一个 ISession 注入到它们的构造函数中,你的控制器应该有任何它们依赖的存储库注入到它们的构造函数中。使用 Ninject 很容易将这一切连接起来。使用这种方法,您可以使用每个请求的事务或(更好的 imo)在操作方法中管理事务。

这是我在 NinjectWebCommon 中使用 Ninject 设置 NHibernate 的方法。您的问题的根本原因可能是您将 ISession 绑定在请求范围内并将其存储在 HttpContext 中,这是不必要的。我也很困惑为什么你有两组域和查询绑定。

    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;
        }
    }

使用事务的示例控制器操作。管理存储库之外的事务很重要,原因如下:

  • 允许多个存储库参与事务
  • 允许控制器设置事务边界(工作单元)
  • 允许在事务中发生延迟加载
  • 如果使用二级缓存,则读取操作需要事务。即使不使用缓存,我认为这是最佳实践

    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 回答