我们有一个ASP.Net 4 / MVC 3混合Web应用程序,它使用NInject 3和(Fluent)NHibernate 3.2。 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] ---&gt ; System.Data.SqlClient.SqlException:不允许启动新请求,因为它应该带有有效的事务描述符。
仅举四个例子。所有这些都有类似的风格 - 它们似乎都与ADO.Net作为NHibernate基础的交易管理有关。
现在,我们NH实施的一些细节:
以下是我们的存储库中的两种方法。
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;
}
为你们和gals提供更多代码。这是我们的IoC Container配置的相关部分。
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; }
请注意,我们使用两个会话,一个用于普通域de / hydration,一个用于CQRS,因此容器中的绑定对。
答案 0 :(得分:1)
错误消息表明您未正确管理交易。我认为根本原因是你正在处理存储库方法中的事务,在我看来这是一个非常糟糕的设计。您的存储库应该将一个ISession注入其构造函数中,并且您的控制器应该具有它们依赖于注入其构造函数的任何存储库。使用Ninject可以轻松完成这一切。使用这种方法,您可以使用每个请求事务或(更好地imo)管理操作方法中的事务。
以下是我在NinjectWebCommon中使用Ninject设置NHibernate的方法。您的问题的根本原因可能是您在请求范围中绑定了ISession并将其存储在HttpContext中,这是不必要的。我也很困惑为什么你有两组Domain和Query绑定。
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);
}
}
答案 1 :(得分:0)
您似乎使用了错误的上下文管理器,请检查您是否使用了WebSessionContext。此上下文管理器将您的会话绑定到当前调用的httpcontext而不是线程。现在在加载(蜘蛛)下发生什么,当你使用ThreadStaticSessionContext时,会话将“跳转”到另一个'call'。