IUnitOfWork
接口using System;
public interface IUnitOfWork : IDisposable
{
void Commit();
}
然后我创建一个名为
IUnitOfWorkFactory
的抽象工厂接口using System.Transactions;
public interface IUnitOfWorkFactory
{
IUnitOfWork GetUnitOfWork(IsolationLevel isolationLevel);
}
然后创建我的
IUnitOfWork
的默认实现称为TransactionScopeUnitOfWork
using System;
using System.Transactions;
public class TransactionScopeUnitOfWork : IUnitOfWork
{
private bool disposed = false;
private readonly TransactionScope transactionScope;
public TransactionScopeUnitOfWork(IsolationLevel isolationLevel)
{
this.transactionScope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = isolationLevel,
Timeout = TransactionManager.MaximumTimeout
});
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
this.transactionScope.Dispose();
}
disposed = true;
}
}
public void Commit()
{
this.transactionScope.Complete();
}
}
然后我创建了工厂,以返回名为
TransactionScopeUnitOfWorkFactory
using System.Transactions;
public class TransactionScopeUnitOfWorkFactory : IUnitOfWorkFactory
{
public IUnitOfWork GetUnitOfWork(IsolationLevel isolationLevel)
{
return new TransactionScopeUnitOfWork(isolationLevel);
}
}
创建工厂的原因是允许DI(依赖关系注入)框架根据配置使用不同的工作单元实现。
如果将
TransactionScopeUnitOfWorkFactory
映射到DI容器中的IUnitOfWorkFactory
,则需要一些示例在应用程序中使用它的代码可能是:public class Test
{
private readonly IUnitOfWorkFactory unitOfWorkFactory;
private readonly IRepository testRepository;
public Test(
IRepository testRepository,
IUnitOfWorkFactory unitOfWorkFactory)
{
this.testRepository = testRepository;
this.unitOfWorkFactory = unitOfWorkFactory;
using (IUnitOfWork unitOfWork = this.unitOfWorkFactory.GetUnitOfWork(IsolationLevel.Serializable))
{
this.testRepository.Delete(1); // Some valid CRUD
unitOfWork.Commit();
}
}
我问这似乎是一个好的实现。我是否缺少任何内容?
我想要一个
IUnitOfWork
接口,我可以在所有应用程序中使用它,而不必担心以后进行维护。有任何意见吗?#1 楼
泄漏抽象public interface IUnitOfWork : IDisposable
{
void Commit();
}
我喜欢这里只有
Commit
方法-它使IUnitOfWork
界面很好地隔离/集中。但是,指定所有实现也必须实现IDisposable
的接口是一个泄漏的抽象-您正在考虑一个特定的实现,并且该实现正在泄漏到抽象中:如果我想要一个不需要public interface IUnitOfWorkFactory
{
IUnitOfWork GetUnitOfWork(IsolationLevel isolationLevel);
}
GetUnitOfWork
听起来像“工厂”隐藏了Singleton之类的东西(名称让人联想到“ GetInstance”)-我喜欢抽象工厂当它们公开单个无参数Create
方法,并从构造函数中获取其依赖关系时; IsolationLevel
还是一个泄漏到抽象中的实现细节-一个特定的实现需要一个“隔离级别”,并且该实现细节也泄漏到了抽象中。我将
: IDisposable
移到TransactionScopeUnitOfWork
,因为该特定实现必须实现IDisposable
。然后我将抽象工厂接口更改为如下所示:
public interface IUnitOfWorkFactory
{
IUnitOfWork Create();
}
创建
TransactionScopeUnitOfWork
的特定实现将负责指定IsolationLevel
:public class TransactionScopeUnitOfWorkFactory : IUnitOfWorkFactory
{
private readonly IsolationLevel _isolationLevel;
public TransactionScopeUnitOfWorkFactory(IsolationLevel isolationLevel)
{
_isolationLevel = isolationLevel;
}
public IUnitOfWork Create()
{
return new TransactionScopeUnitOfWork(_isolationLevel);
}
}
使用Ninject等IoC容器,您可以根据需要轻松地指定
IsolationLevel.Serializable
或IsolationLevel.ReadCommitted
,具体取决于工厂依赖项是注入到Smurf
还是FooBar
的构造函数中。我已经说过注意,我很少需要使用Ninject来实现一个抽象工厂:您可以轻松地将DI配置为在类具有
IUnitOfWork
依赖项的情况下注入匿名方法的结果-injected:您只需使用.ToFactory
进行绑定,然后让Ninject进行艰苦的工作即可。如果客户端代码只需要依赖提供
IUnitOfWork
方法的Commit
,就不必为配置事务隔离级别而烦恼。如果客户端代码需要了解隔离级别,那么它所了解的接口背后的实现将比其了解的更多。评论
\ $ \ begingroup \ $
我同意您的许多观点,例如将工厂方法名称更改为Create。发现。但是您提到,客户端代码不需要了解事务隔离级别。我认为应该。工作单位是每个交易操作。所以我认为这是相关的。这就是为什么我要保持这种灵活性。我没有像您建议的那样将其硬编码到我的容器中。看我的例子。另外,我将IDisposable放入IUnitOfWork中,因为我想放入using块。如果不存在它,您将如何建议使用它?
\ $ \ endgroup \ $
–艾萨·弗拉姆(Issa Fram)
2015年1月15日17:23
\ $ \ begingroup \ $
在易用性(即使用友好性)与您希望事物变得“纯净”之间取得平衡-我只是指出,并非所有实现都可能需要一次性使用。至于隔离级别,除非立即使用SQL Server 2014,否则如果一时改变就可能会遇到意想不到的问题。
\ $ \ endgroup \ $
– Mathieu Guindon♦
15年1月15日在18:07
\ $ \ begingroup \ $
哎呀不是我对你的期望。到目前为止,您所说的一切都是违背的。 “ ...那么,它比所呈现的接口背后的实现要了解的更多。”
\ $ \ endgroup \ $
–艾萨·弗拉姆(Issa Fram)
15年1月15日在18:39
\ $ \ begingroup \ $
实际上,更简单的方法是让IoC容器处理UoW的生命周期-这就是IoC容器要做的事情!
\ $ \ endgroup \ $
– Mathieu Guindon♦
15年1月15日在18:57
\ $ \ begingroup \ $
我完全不同意将IDisposable接口放在IUnitOfWork上是泄漏的。它只是说所有工作单元都必须被处理掉,就像OP所说的那样,允许它们在使用语句时使用。如果某些实现无需处理,那就没关系。我认为遵守Liskov替换原则要好得多-程序中的对象应该可以用其子类型的实例替换,而无需更改该程序的正确性。
\ $ \ endgroup \ $
–craftworkgames
15年1月16日,0:30
#2 楼
Disposable
模式太复杂。看来您忘记了从Dispose(false)
致电~Finalize()
。但是,由于TransactionScopeUnitOfWork
不包含非托管资源,因此可以简化实现。public void Dispose()
{
transactionScope.Dispose();
}
就足够了。
从
public Test(
IRepository testRepository,
IUnitOfWorkFactory unitOfWorkFactory)
{
this.testRepository = testRepository;
this.unitOfWorkFactory = unitOfWorkFactory;
using (IUnitOfWork unitOfWork = this.unitOfWorkFactory.GetUnitOfWork(IsolationLevel.Serializable))
{
this.testRepository.Delete(1); // Some valid CRUD
unitOfWork.Commit();
}
}
如果我正确理解
TransactionScope
的目的,那是不对的。这些接口的实现应该兼容,因此,如果IUnitOfWork
基于TransactionScope
,则IRepository
应该基于SqlConnection
(或OracleConnection
或其他支持事务范围的东西)。我不确定,但是尝试考虑在单个工厂中创建存储库和UoW:
public interface IDataLayerFactory
{
IRepository CreateRepository();
IUnitOfWork CreateUnitOfWord(IsolationLevel isolationLevel);
}
这种设计使关系明确。
评论
\ $ \ begingroup \ $
当您声明“您忘记从〜Finalize()调用Dispose(false)”时,不确定您的意思。没有不受管理的资源,因此我故意将终结器排除在外。我最初将简单的this.transactionScope.Dispose()保留在那里,但是Code Analysis抱怨没有实现Disposable Pattern。我不知道.NET的所有详细信息,但是如果有两次调用Dispose()的机会,那么我想确保我正确执行了所有操作。
\ $ \ endgroup \ $
–艾萨·弗拉姆(Issa Fram)
15年1月15日在19:23
\ $ \ begingroup \ $
您是正确的,TransactionScope仅适用于某些类(前提是它们具有链接的实现),是的,在这种情况下,我的存储库恰好适用于TransactionScope。但是,那是一种依赖。我只是在IoC容器中进行正确的分配。如果不正确,将没有工作单元。但是,回购协议仍会执行其常规任务。您认为这很糟糕吗?我的仓库是通用的,用在我的容器中,因此在工厂中如何工作?
\ $ \ endgroup \ $
–艾萨·弗拉姆(Issa Fram)
2015年1月15日19:30
\ $ \ begingroup \ $
@IssaFram正如我所说,“一次性模式”太复杂了。 :)请注意,您的Dispose方法调用了Dispose(true),但从未调用过Dispose(false)。因此,您可以简化代码。
\ $ \ endgroup \ $
–马克·舍甫琴科
2015年1月15日19:57
\ $ \ begingroup \ $
还请注意,配置字段使代码与简单修改不同。我防止多次调用托管Dispose。
\ $ \ endgroup \ $
–艾萨·弗拉姆(Issa Fram)
15年1月15日在20:21
\ $ \ begingroup \ $
根据第二部分:要显示这两个实体之间的关系,只要它们都属于同一层(相同或相邻的名称空间)就足够了。在这种情况下,我没有明确的建议。
\ $ \ endgroup \ $
–马克·舍甫琴科
15年1月15日在20:27
评论
欢迎使用代码审查!这是一个很好的第一个问题,希望您得到一些好评!