所以,有一些旧的代码,我保持一个错误。它会导致一些轻微的数据损坏,所以它相当严重。我找到了根本原因,并且制作了一个可靠的示例应用程序来重现该错误。我想尽可能减少对现有应用程序的影响,但我很挣扎。使用DI来拦截器在传统的代码添加到NHibernate的会议
的缺陷在于数据访问层英寸更具体地说,拦截器如何被注入到新的Nhibernate会话中。拦截器用于在保存或刷新时设置特定的实体属性。该属性LoggedInPersonID几乎可以在我们所有的实体上找到。所有实体使用的数据库架构的CodeSmith模板生成,所以LoggedInPersonID属性对应于被发现在数据库中几乎所有的表列。与其他一些列和触发器一起,它用于跟踪哪个用户在数据库中创建和修改记录。任何插入或更新数据的事务都需要提供LoggedInPersonID值,否则事务将失败。
每当客户端需要一个新的会话,就会调用的openSession在SessionFactory的(不是NHibernate的SessionFactory的,但包装)制成。下面的代码示出了会话工厂包装类的相关部分:
public class SessionFactory
{
private ISessionFactory sessionFactory;
private SessionFactory()
{
Init();
}
public static SessionFactory Instance
{
get
{
return Nested.SessionFactory;
}
}
private static readonly object _lock = new object();
public ISession OpenSession()
{
lock (_lock)
{
var beforeInitEventArgs = new SessionFactoryOpenSessionEventArgs(null);
if (BeforeInit != null)
{
BeforeInit(this, beforeInitEventArgs);
}
ISession session;
if (beforeInitEventArgs.Interceptor != null
&& beforeInitEventArgs.Interceptor is IInterceptor)
{
session = sessionFactory.OpenSession(beforeInitEventArgs.Interceptor);
}
else
{
session = sessionFactory.OpenSession();
}
return session;
}
}
private void Init()
{
try
{
var configuration = new Configuration().Configure();
OnSessionFactoryConfiguring(configuration);
sessionFactory = configuration.BuildSessionFactory();
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.Message);
while (ex.InnerException != null)
{
Console.Error.WriteLine(ex.Message);
ex = ex.InnerException;
}
throw;
}
}
private void OnSessionFactoryConfiguring(Configuration configuration)
{
if(SessionFactoryConfiguring != null)
{
SessionFactoryConfiguring(this, new SessionFactoryConfiguringEventArgs(configuration));
}
}
public static event EventHandler<SessionFactoryOpenSessionEventArgs> BeforeInit;
public static event EventHandler<SessionFactoryOpenSessionEventArgs> AfterInit;
public static event EventHandler<SessionFactoryConfiguringEventArgs> SessionFactoryConfiguring;
public class SessionFactoryConfiguringEventArgs : EventArgs
{
public Configuration Configuration { get; private set; }
public SessionFactoryConfiguringEventArgs(Configuration configuration)
{
Configuration = configuration;
}
}
public class SessionFactoryOpenSessionEventArgs : EventArgs
{
private NHibernate.ISession session;
public SessionFactoryOpenSessionEventArgs(NHibernate.ISession session)
{
this.session = session;
}
public NHibernate.ISession Session
{
get
{
return this.session;
}
}
public NHibernate.IInterceptor Interceptor
{
get;
set;
}
}
/// <summary>
/// Assists with ensuring thread-safe, lazy singleton
/// </summary>
private class Nested
{
internal static readonly SessionFactory SessionFactory;
static Nested()
{
try
{
SessionFactory = new SessionFactory();
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
throw;
}
}
}
}
拦截通过BeforeInit事件注入。下面是拦截器实现:
public class LoggedInPersonIDInterceptor : NHibernate.EmptyInterceptor
{
private int? loggedInPersonID
{
get
{
return this.loggedInPersonIDProvider();
}
}
private Func<int?> loggedInPersonIDProvider;
public LoggedInPersonIDInterceptor(Func<int?> loggedInPersonIDProvider)
{
SetProvider(loggedInPersonIDProvider);
}
public void SetProvider(Func<int?> provider)
{
loggedInPersonIDProvider = provider;
}
public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState,
string[] propertyNames, NHibernate.Type.IType[] types)
{
return SetLoggedInPersonID(currentState, propertyNames);
}
public override bool OnSave(object entity, object id, object[] currentState,
string[] propertyNames, NHibernate.Type.IType[] types)
{
return SetLoggedInPersonID(currentState, propertyNames);
}
protected bool SetLoggedInPersonID(object[] currentState, string[] propertyNames)
{
int max = propertyNames.Length;
var lipid = loggedInPersonID;
for (int i = 0; i < max; i++)
{
if (propertyNames[i].ToLower() == "loggedinpersonid" && currentState[i] == null && lipid.HasValue)
{
currentState[i] = lipid;
return true;
}
}
return false;
}
}
下面是应用程序用来注册BeforeInit事件处理一个辅助类:
public static class LoggedInPersonIDInterceptorUtil
{
public static LoggedInPersonIDInterceptor Setup(Func<int?> loggedInPersonIDProvider)
{
var loggedInPersonIdInterceptor = new LoggedInPersonIDInterceptor(loggedInPersonIDProvider);
ShipRepDAL.ShipRepDAO.SessionFactory.BeforeInit += (s, args) =>
{
args.Interceptor = loggedInPersonIdInterceptor;
};
return loggedInPersonIdInterceptor;
}
}
}
的错误是在我们的网络服务(WCF SOAP)尤为突出。 Web服务端点的绑定都是basicHttpBinding的。为每个客户端请求创建一个新的Nhibernate会话。 LoggedInPersonIDInterceptorUtil.Setup方法在客户端通过身份验证后被调用,并在关闭中捕获经过身份验证的客户端ID。此外,还有以达到代码比赛之前另一个客户端请求触发到通话SessionFactory.OpenSession注册一个事件处理程序BeforeInit事件有不同的开合 - ,因为它在BeforeInit事件的调用的最后一个处理列表“获胜”,可能会返回错误的拦截器。的错误时,两个客户几乎同时提出请求,而且当两个客户都呼吁用不同的执行时间(一个来自认证比另一个需要更长时间的openSession)不同的Web服务方法通常发生。
除了数据损坏之外,还有内存泄漏,因为事件处理程序没有取消注册?这可能是我们的Web服务流程每天至少回收一次的原因?
这真的看起来像BeforeInit(和AfterInit)事件需要去。我可能会改变OpenSession方法的签名,并添加一个IInterceptor参数。但是这会打破很多的代码,并且我不想在检索会话时传入拦截器 - 我希望这是透明的。由于拦截器在所有使用DAL的应用程序中都是一个交叉问题,因此依赖注入是一个可行的解决方案吗?统一用于我们应用程序的其他一些领域。
在正确的方向的任何微移将不胜感激:)
这听起来很有希望!谢谢你的想法,我会明天测试它,并将其标记为答案,如果它工作:) – matsho