给定我的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.SerializableIsolationLevel.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