【问题标题】:C# - TransactionScope outside of a using block?C# - 使用块之外的 TransactionScope?
【发布时间】:2018-12-12 04:07:19
【问题描述】:

我的查询比标题显示的要复杂一些。也许这是一个愚蠢的问题,但是我在网上搜索时找不到任何确定的答案。目前,我正在以自己的风格实现存储库/工作单元模式,它看起来有点像这样:

// Note: methods are async for conventions, not because
// they're truly async
public interface IUnitOfWork
{
    Task Begin();

    // The Task<int> is the numbers of rows affected by this commit
    Task<int> Commit();

    Task Rollback();
}

存储库或多或少可以这样表示:

public interface IWriteableRepository<T>
    where T : class
{
     EntityEntry<T> Insert(T item);

     // Other CRUD methods removed for brevity; they're
     // of similar signatures
}

这个想法是IUnitOfWork 将在内部保存一些TransactionScope 实例并处理相应的逻辑。

然后我有两个担忧。首先,如果每个IUnitOfWorkIWriteableRepository&lt;T&gt; 实例都注入了DbContext 的不同实例(我暂时使用EntityFrameworkCore),那么在下面的代码中调用DbContext.BeginTransactionAsync() 会为两者生成一个事务范围吗?

await this.UnitOfWork.Begin();

this.Repository.Insert(someEntity);

var rows = await this.UnitOfWork.Commit();

换句话说,存储库是只对调用Begin()时创建的事务进行操作,还是完全独立操作?

我关心的第二个问题与实现IUnitOfWork 接口有关。到目前为止,我的方法大致是

public class UnitOfWork : IUnitOfWork
{
    public UnitOfWork(DbContext context)
    {
        this.Context = context;
    }

    private DbContext Context { get; set; }

    private TransactionScope Transaction { get; set; }

    public async Task Begin()
    {
        if (this.Scope == null)
        {
            this.Transaction = await this.Context
                .Database
                .BeginTransactionAsync();
        }
    }

    public async Task<int> Commit()
    {
        if (this.Scope != null)
        {
            var rows = await this.Context.SaveChangesAsync(false);

            this.Scope.Commit();

            this.Context.AcceptAllChanges();

            return rows;
        }
    }

    public Task Rollback()
    {
        if (this.Scope != null)
        {
            this.Scope.Rollback();
            this.Scope.Dispose();

            this.Scope = null;
        }

        return Task.CompletedTask;
    }
}

我主要不确定Rollback() 方法是否可以改进。我觉得明确地处理对象是不正确的。我还有其他方法可以摆脱TransactionScope吗?

【问题讨论】:

  • 如果您使用实体框架,我的诚实意见是您应该放弃存储库模式和工作单元,让您自己免于维护噩梦,将简单的逻辑隐藏在神秘的抽象层后面。 EF 已经为您实现了这些设计和模式。此外,如果您只想回滚数据库事务,请在 EF 中使用更现代的 TransactionBegin。除了将来必须重新设计之外,您的方法真的没有什么好处。
  • 可能会有所帮助:stackoverflow.com/a/51781877/5779732

标签: c# entity-framework-core repository-pattern unit-of-work transactionscope


【解决方案1】:

就我而言,这是我提出的解决方案 - 我绝对不建议遵循它,而且我确信我的团队在遇到问题时必须解决这些问题......

因为我们需要多个数据库引擎(Mongo 和 EF/SQL),所以我们将与数据库的交互封装在存储库和工作单元模式中。我们所有的存储库都是按数据库引擎实现的,例如IMongoRepository&lt;T&gt; : IWriteableRepository&lt;T&gt;IWriteableRepository&lt;T&gt; 无法抽象的方法由IMongoRepository&lt;T&gt; 实现。这很有效,我不介意使用这种模式。

IUnitOfWork 也是按数据库引擎实现的,因为 Mongo、SQL 等会以不同方式处理事务。通过使用工厂解决了在维护可注入对象的同时共享上下文的问题,即类似

public class FooService
{
    public FooService(
        IUnitOfWorkFactory<EntityFrameworkUnitOfWork> factory,
        IRepositoryContext context,
        IWriteableRepository<Bar> repository)
    {
        this.UnitOfWorkFactory = factory;
        this.Context = context;
        this.Repository = repository;
    }

    private IUnitOfWorkFactory<EntityFrameworkUnitOfWork> UnitOfWorkFactory { get; set; }

    private IRepositoryContext Context { get; set; }

    private IWriteableRepository<Bar> Repository { get; set; }

    public bool RemoveBar(int baz)
    {
        // IUnitOfWorkFactory<T>.Begin(IRepositoryContext)
        //     where T : IUnitOfWork, new()
        // 
        // 1) Creates a new IUnitOfWork instance by calling parameterless constructor
        // 2) Call UseContext(IRepositoryContext) on UoW, passing in the context;
        //        This causes the UoW to use the passed-in context
        // 3) Calls .Begin() on the UoW
        // 4) Returns the UoW
        using (var unitOfWork = this.UnitOfWorkFactory.Begin(this.Context))
        {
            var bar = this.Repository
                .Query()
                .First(x => x.Baz == baz);

            this.Repository.Remove(bar);

            var (success, rows) = unitOfWork.Commit();

            return success && rows > 0;
        }
    }
}

EntityFrameworkUnitOfWork(或任何IUnitOfWork)可以任意实现BeginCommitRollbackIUnitOfWork 还实现了 IDisposable 以确保清理底层事务对象。使用相同的上下文还可以确保事务肯定会应用于使用该上下文的存储库。

此外,如果有检查以确保一次只打开一个事务,则可以传入IUnitOfWork 而不是工厂;但是,为了消除这种与实现的耦合,我们创建了一个工厂。这不仅确保我们每个 using 块只有一个交易,我们还能够拥有一个 using 块,而无需在我们的消费代码中触及 IUnitOfWork 的构造函数。

作为免责声明,我完全同意您不应将 ORM 包装在存储库中。它会混淆你的数据库代码并增加不必要的复杂性。我们正在使用这些存储库来努力使我们的数据库交互在做简单的事情时不可知。否则,我们会在存储库实现中变得更加具体,从而消除其他存储库模式实现所遭受的许多魔力。一般来说,一旦方法超越了单独的记录操作,数据库引擎和驱动程序就会有不同的想法来处理它。

最后一点:很明显,如果您注入的存储库与您注入的上下文不匹配(例如,IMongoContextIEntityFrameworkRepository&lt;Bar&gt;),您的代码将不会在事务中运行数据库。这不是问题的原因是

  1. 在大多数情况下,使用与存储库中不同的上下文已经很荒谬了
  2. 您必须在消费代码中管理上下文和存储库,因此会意识到冲突的上下文

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多