【发布时间】:2019-10-31 20:40:54
【问题描述】:
所以,我正在维护的一些遗留代码存在错误。它会导致一些轻微的数据损坏,因此相当严重。我已经找到了根本原因,并制作了一个可以可靠地重现该错误的示例应用程序。我想尽可能减少对现有应用程序的影响来修复它,但我很挣扎。
错误在于数据访问层。更具体地说,如何将拦截器注入到新的 Nhibernate Session 中。拦截器用于在保存或刷新时设置特定的实体属性。 LoggedInPersonID 属性几乎可以在我们所有的实体中找到。所有实体都是使用数据库模式从 CodeSmith 模板生成的,因此 LoggedInPersonID 属性对应于在数据库中几乎所有表上都可以找到的列。与其他几个列和触发器一起,它用于跟踪哪个用户在数据库中创建和修改了一条记录。任何插入或更新数据的事务都需要提供 LoggedInPersonID 值,否则事务将失败。
每当客户端需要一个新会话时,都会在 SessionFactory(不是 Nhibernate 的 SessionFactory,而是一个包装器)中调用 OpenSession。下面的代码展示了 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;
}
}
}
该错误在我们的 Web 服务 (WCF SOAP) 中尤为突出。 Web 服务端点绑定都是 basicHttpBinding。为每个客户端请求创建一个新的 Nhibernate 会话。 LoggedInPersonIDInterceptorUtil.Setup 方法在客户端通过身份验证后调用,并在闭包中捕获经过身份验证的客户端 ID。然后,在另一个客户端请求使用不同的闭包向 BeforeInit 事件注册事件处理程序之前,就会有一个代码竞赛触发对 SessionFactory.OpenSession 的调用 - 因为,它是 BeforeInit 事件的调用列表中“获胜”的最后一个处理程序,可能会返回错误的拦截器。该错误通常发生在两个客户端几乎同时发出请求时,但也发生在两个客户端客户端以不同的执行时间调用不同的 Web 服务方法(一个从身份验证到 OpenSession 的时间比另一个要长)。
除了数据损坏之外,还有内存泄漏,因为事件处理程序没有取消注册?这可能是我们的 Web 服务进程每天至少回收一次的原因?
看起来确实需要 BeforeInit(和 AfterInit)事件。我可以更改 OpenSession 方法的签名,并添加一个 IInterceptor 参数。但这会破坏 很多 代码,而且我不想在检索会话时传入拦截器 - 我希望这是透明的。由于拦截器在所有使用 DAL 的应用程序中都是一个横切关注点,依赖注入会是一个可行的解决方案吗? Unity 用于我们应用程序的其他一些领域。
任何朝着正确方向的轻推将不胜感激:)
【问题讨论】:
标签: c# nhibernate dependency-injection unity-container