我们有一个使用 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,因此容器中的绑定对。