我目前正在NHibernate之上开发一个工作单元和存储库模式(备注:我没有为一个或另一个模式做出决定,因此请不要讨论存储库模式对已经实现一个模式的am ORM的有用性(。我首先根据文档构建了一个单例(使用IoC配置(会话管理器,用于创建工作单元实例(为了可读性,删除了私有方法(:
public sealed class SessionManager : ISessionManager
{
#region Members
/// <summary>
/// lock to manage thread safe access to the dictionary.
/// </summary>
private readonly ReaderWriterLockSlim _lock;
/// <summary>
/// NHibernate sessionfactory to create sessions.
/// </summary>
private readonly ISessionFactory _factory;
/// <summary>
/// Units of Work that are already in use.
/// </summary>
private readonly Dictionary<string, IUnitOfWork> _units;
/// <summary>
/// Flag indicating if the manager got disposed.
/// </summary>
private bool _disposed;
#endregion
#region Constructors
/// <summary>
/// Creates a new Manager with the given Config.
/// </summary>
/// <param name="config"></param>
public SessionManager(NHibernate.Cfg.Configuration config)
{
_lock = new ReaderWriterLockSlim();
_units = new Dictionary<string, IUnitOfWork>();
_factory = config
.Configure()
.AddAssembly(typeof(SessionManager).Assembly.FullName)
.BuildSessionFactory();
_disposed = false;
}
#endregion
#region Methods
/// <summary>
/// Either creates or returns an existing unit of work.
/// </summary>
/// <param name="identifier">identifier of the uow</param>
/// <param name="access">the data access that is needed for this uow.</param>
/// <returns></returns>
public IUnitOfWork Create(string identifier, DataAccess access)
{
Throw.IfNull(identifier, nameof(identifier));
if (TryGetUnitOfWork(identifier, out var unit))
return unit;
return CreateOrReturn(identifier, access);
}
/// <summary>
/// Disposes the given instance.
/// </summary>
/// <param name="unitOfWork">The unit of work that should get disposed.</param>
public void DisposeUnitOfWork(IUnitOfWork unitOfWork)
{
Throw.IfNull(unitOfWork, nameof(unitOfWork));
try
{
_lock.EnterWriteLock();
if (_units.ContainsValue(unitOfWork))
{
var key = _units.FirstOrDefault(x => x.Value.Equals(unitOfWork)).Key;
if (!string.IsNullOrWhiteSpace(key))
_units.Remove(key);
}
unitOfWork.Dispose();
}
finally
{
_lock.ExitWriteLock();
}
unitOfWork.Dispose();
}
/// <summary>
/// Disposes managed and unmanaged ressources.
/// </summary>
/// <param name="disposing">Flag indicating if the call happened in the public <see cref="Dispose"/> method or the Finalizer.</param>
void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
foreach (var unit in _units)
unit.Value.Dispose();
_factory.Dispose();
_lock.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
_disposed = true;
}
}
/// <summary>
/// Disposes managed and unmanaged ressources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}
工作单元包含用于创建事务或所需存储库的ISession
或IStatelessSession
。
public sealed class UnitOfWork : IUnitOfWork
{
#region Members
private bool _disposed;
public string Identifier { get; }
public DataAccess Access { get; }
private readonly ISession _session;
private readonly IStatelessSession _statelessSession;
#endregion
#region Constructors
private UnitOfWork(DataAccess access, string identifier)
{
Access = access;
Identifier = identifier;
_disposed = false;
}
internal UnitOfWork(DataAccess access, string identifier, ISession session)
: this(access, identifier)
{
_session = session;
}
internal UnitOfWork(DataAccess access, string identifier, IStatelessSession session)
: this(access, identifier)
{
_statelessSession = session;
}
#endregion
#region Methods
public void CloseTransaction(IGenericTransaction transaction)
{
transaction.Dispose();
}
public IGenericTransaction OpenTransaction()
{
if (_session != null)
return new GenericTransaction(_session.BeginTransaction());
if (_statelessSession != null)
return new GenericTransaction(_statelessSession.BeginTransaction());
throw new InvalidOperationException("You tried to create a transaction without having a vaild session.");
}
public IGenericTransaction OpenTransaction(IsolationLevel level)
{
if (_session != null)
return new GenericTransaction(_session.BeginTransaction(level), level);
if (_statelessSession != null)
return new GenericTransaction(_statelessSession.BeginTransaction(level), level);
throw new InvalidOperationException("You tried to create a transaction without having a vaild session.");
}
public bool Equals(IUnitOfWork other)
{
if (other == null)
return false;
return Access == other.Access;
}
public IRepository<T> GetRepository<T>() where T : Entity<T>
{
switch (Access)
{
case DataAccess.Statefull:
return new StatefullRepository<T>(_session);
case DataAccess.Stateless:
return new StatelessRepository<T>(_statelessSession);
default:
throw new ArgumentException("Cannot determine which repository is needed.", nameof(Access));
}
}
#region IDisposable Support
void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_session != null)
_session.Dispose();
if (_statelessSession != null)
_statelessSession.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
_disposed = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// ~UnitOfWork() {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
#endregion
}
可以看到,存储库是使用ISession
或IStatelessSession
创建的。这两个接口都实现了IDisposable
接口,这意味着它们应该被丢弃。因此,我的存储库也实现了IDisposable
。然而,问题就在这里。理论上,我可以从一个工作单元中创建尽可能多的存储库,例如:
public void UpdatePets(List<Cat> felines, List<Dog> carnines, ISessionManager manager)
{
var uow = manager.Create("Pets", DataAccess.Statefull);
using (var t = uow.OpenTransaction())
{
using (var catRepo = uow.GetRepository<Cat>())
{
catRepo.Add(felines);
t.Commit();
}
}
//Some Businesslogic that forbids using one Transaction to mitigate the problem of an already disposed ISession or IStatelessSession.
using(var t = uow.OpenTransaction())
{
using (var dogRepo = uow.GetRepository<Dog>())
{
dogRepo.Add(carnines);
t.Commit();
}
}
}
如果这将是消耗我的工作单元的服务,并且我将使用这样的"标准"IDisposable
模式:
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_session != null)
_session.Dispose();
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
第二个存储库将抛出ObjectDisposeException,因为第二个库中的ISession I引用来自我的工作单元,而当我的第一个存储库离开using块时,该存储库已经被释放。因此,为了克服这个问题,我会在我的存储库中做以下操作:
public void Dispose()
{
_session = null;
GC.SuppressFinalize(this);
}
然而,这感觉是错误的,因为我没有正确地处理对我应该处理的对象的引用,而是"闭上眼睛,忘记它",这在编程中可能永远都不是一个好的解决方案。因此,我的问题基本上是:"有没有一种好的方法来正确处理可能比持有引用的对象寿命更长的对象?">
您需要建立所有权,并使用它来确定哪个类负责清理每个"东西"。无论有多少其他类与它交互,它都是应该调用Dispose
的所有者,而且只有所有者
所有权需要是独占和(包装类除外(嵌套1-所有者的生存期至少需要与所拥有的对象一样长。通过遵循这两条规则,我们可以确保Dispose a(在不再需要对象时被调用一次2b(。
存储库是"拥有"会话的错误做法。多个存储库可以被赋予相同的会话(因此,如果它们是所有者,我们就失去了独占属性(,正如您自己的标题所暗示的,它们的寿命比它短。
的一些一般经验法则
- 作为参数传递给实例方法的对象通常不会归实例所有
- 一个由实例创建的对象,其使用寿命超过单个方法调用的使用寿命,将由该实例所有
- 由实例方法创建的对象,并且其生存期不超过单个方法调用的生存期,将归该方法所有
1对于包装器,内部对象将倾向于首先创建,可能进行配置,然后传递给包装器对象。因此,包装器对象的生存期在内部对象之后开始。
2并非严格要求。一次性用品应该能容忍多次调用Dispose,但这更多的是为了安全带和背带的防御,而不是所需的模式。