在做了更多的研究并听取了这里的建议之后,我已经能够更接近于找出我的问题。我正在处理一个事务的NHibernate问题,它只在生产中被复制,因为当我们在本地测试时它不会发生。我们有2张桌子预订和房间,每1个预订入口应该有1个房间入口。所以如果我们有802个预订条目,我们应该有802个房间条目。在代码中,首先添加Booking,然后添加包含BookingID列的Room条目,这就是它与Booking表的关系。每周大约有两天我们会发现400个Booking条目和398个Room条目。我们测试了不同的场景并制造了一些错误,当发生这种情况时,所有的东西都被回滚,并显示在日志中。我们还在事务周围添加了日志记录,并且可以看到,即使没有插入Room条目,事务也已成功提交。是否有某种方式,我可以获得会话ID传递到日志记录器或调优当前代码中的东西?这个问题大约每200个请求发生一次。我在想,也许在这些情况下,我们可以有不同的会话,或者Nhibernate没有跟踪第二次插入查询的数据。现在我们在Application_EndRequest中处理会话,不确定在事务提交后显式地处理会话是否会解决任何问题。
// Repository
public class EntityRepository : IRepository
{
private readonly string connectionStringKey;
private NHibernate.ISession context;
protected NHibernate.ISession Context
{
get
{
return (this.context == null || !this.context.IsOpen) ?
(this.context = DataContextFactory.GetContextForConnection(this.connectionStringKey)) : this.context;
}
}
public void Add<TObject>(TObject obj) where TObject : class
{
Context.Save(obj);
}
public NHibernate.ITransaction CreateTransaction()
{
return Context.BeginTransaction(); // flushmode is auto as default
}
}
// Main Method
public Models.BookingRequestResult BookingRoom(Models.Booking booking)
{
ITransaction transaction = null;
try
{
booking.BookingAppID = CodeGenerator.UniqueIdGenerator.Generate(10);
transaction = repository.CreateTransaction();
logger.InfoFormat("Transaction Started for: {0}", booking.BookingAppID );
transaction.Begin(System.Data.IsolationLevel.Serializable);
repository.Add(booking); // booking added
logger.InfoFormat("Booking Added for {0}", booking.BookingAppID);
var RoomAppt = new Rooms { Booking = booking , Type="Room" };
repository.Add(RoomAppt); // room added and the BookingID is the ID of the newly created booking
transaction.Commit();
logger.InfoFormat("Booking: {0} has been committed", booking.BookingAppID); // This is in the logger even when the room does not get added
...
} catch(exception e) {
// any exceptions we log here and rollback transaction
}
}
// Creates SessionFactory for application
public static class DataContextFactory
{
private static IDictionary<string, NHibernate.ISessionFactory> sFactories = new Dictionary<string, NHibernate.ISessionFactory>();
public static NHibernate.ISession GetContextForConnection(string connectionStringKey = null)
{
if (string.IsNullOrEmpty(connectionStringKey)) connectionStringKey = GetCurrentContext();
var context = sContextStore[connectionStringKey];
if(context == null || !context.IsOpen)
{
lock (sCreateSyncRoot)
{
context = sContextStore[connectionStringKey];
if (context == null || !context.IsOpen)
{
if(sFactories.ContainsKey(connectionStringKey))
{
context = sFactories[connectionStringKey].OpenSession();
sContextStore[connectionStringKey] = context;
}
else
{
throw new ArgumentException("This connection string wasn't specified in Db Context configuration.");
}
}
}
}
return context;
}
public static void Initialize(System.Xml.Linq.XElement configNode = null)
{
lock (sInitSyncRoot)
{
if (sContextWrappers != null)
{
throw new InvalidOperationException("Context is already initialized");
}
if (configNode == null)
{
var configFile =
System.IO.Path.Combine(
System.Configuration.ConfigurationManager.AppSettings[ConfigurationTokens.ConfigurationPath],
"DataContextFactory.conf");
if (System.IO.File.Exists(configFile))
{
configNode = System.Xml.Linq.XElement.Load(configFile);
}
}
if (configNode == null)
{
throw new NullReferenceException("No configuration node or file was provided.");
}
var attrib = configNode.Attribute("storeType");
if (attrib == null)
{
throw new NullReferenceException("Store type is undefined");
}
sContextStore = Utilities.ReflectionUtility.Get<IContextStore>(attrib.Value);
attrib = configNode.Attribute("defaultConnectionStringKey");
if (attrib != null)
{
sDefaultConnectionString = attrib.Value;
}
foreach (var dbContext in configNode.Elements("DbContext"))
{
var dbConnectionStringAttribute = dbContext.Attribute("connectionStringKey");
if (dbConnectionStringAttribute == null || string.IsNullOrWhiteSpace(dbConnectionStringAttribute.Value))
{
throw new ArgumentException("No connection string key specified in DataContextFactory.conf.");
}
FluentConfiguration configuration =
ConfigureWrapper(dbContext, dbConnectionStringAttribute.Value);
configuration.Mappings(c => {
c.FluentMappings.Conventions.Add<EnumConvention>();
});
AddMappingAssemblies(dbContext, configuration);
sFactories.Add(dbConnectionStringAttribute.Value, configuration.BuildSessionFactory());
}
}
}
public static void DeInitialize()
{
if (sContextStore != null)
{
var ctxStores = sContextStore.GetAll();
foreach (var ctx in ctxStores)
{
if (ctx.IsOpen)
{
var objCtx = ctx.Close();
}
ctx.Dispose();
}
}
}
}
// Global.asax
protected void Application_EndRequest(object sender, EventArgs args)
{
PatientPoint.Infrastructure.Data.DataContextFactory.DeInitialize();
}
您的repository.CreateTransaction
调用ISession.BeginTransaction
-它以默认隔离级别打开事务。然后尝试使用指定的显式隔离级别打开事务:
transaction = repository.CreateTransaction();
logger.InfoFormat("Transaction Started for: {0}", booking.BookingAppID );
transaction.Begin(System.Data.IsolationLevel.Serializable); //<-- THIS LINE LOOKS WRONG
很可能不支持这样的多个begin事务调用。如来源所述:
一个特定的会话在同一时间最多有一个未提交的ittransaction。
你只需要调用一次ISession.BeginTransaction(isolationLevel)
,不要在开放的事务中调用ITransaction.Begin
。