C#-使用块外的事务范围?

C#-使用块外的事务范围?,c#,entity-framework-core,repository-pattern,unit-of-work,transactionscope,C#,Entity Framework Core,Repository Pattern,Unit Of Work,Transactionscope,我的问题比标题上的问题复杂一点。也许这是一个愚蠢的问题,但我在网上搜索时找不到任何确定的答案。目前,我正在以自己的风格实现存储库/工作单元模式,它看起来有点像这样: // Note: methods are async for conventions, not because // they're truly async public interface IUnitOfWork { Task Begin(); // The Task<int> is the numb

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

// 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();
}
换句话说,存储库是只对调用
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;
    }
}
公共类UnitOfWork:IUnitOfWork
{
公共UnitOfWork(DbContext上下文)
{
this.Context=Context;
}
私有DbContext上下文{get;set;}
私有事务范围事务{get;set;}
公共异步任务Begin()
{
if(this.Scope==null)
{
this.Transaction=等待this.Context
数据库
.BeginTransactionAsync();
}
}
公共异步任务提交()
{
if(this.Scope!=null)
{
var rows=wait this.Context.saveChangesSync(false);
this.Scope.Commit();
this.Context.acceptualchanges();
返回行;
}
}
公共任务回滚()
{
if(this.Scope!=null)
{
this.Scope.Rollback();
this.Scope.Dispose();
this.Scope=null;
}
返回Task.CompletedTask;
}
}

我很不确定
Rollback()
方法是否可以改进。我觉得显式地处理对象是不正确的。有没有其他方法可以让我摆脱
交易范围

在我的情况下,这就是我提出的解决方案-我绝对不建议遵循它,我相信我的团队在出现问题时必须解决一些问题

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

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;
        }
    }
}
公共类服务
{
公共食品服务(
我的工厂,
IRepositoryContext,
IWriteableRepository(存储库)
{
this.UnitOfWorkFactory=工厂;
this.Context=Context;
this.Repository=Repository;
}
私有IUnitOfWorkFactory UnitOfWorkFactory{get;set;}
私有IRepositoryContext{get;set;}
专用IWriteableRepository存储库{get;set;}
公共厕所拆除栏(内巴兹)
{
//IUnitOfWorkFactory.Begin(IRepositoryContext)
//其中T:i工作单元,new()
// 
//1)通过调用无参数构造函数创建新的IUnitOfWork实例
//2)在UoW上调用UseContext(IRepositoryContext),传入上下文;
//这将导致UoW使用传入的上下文
//3)在UoW上调用.Begin()
//4)返回UoW
使用(var unitOfWork=this.UnitOfWorkFactory.Begin(this.Context))
{
var bar=this.Repository
.Query()
.第一(x=>x.Baz==Baz);
this.Repository.Remove(bar);
var(success,rows)=unitOfWork.Commit();
返回成功&&rows>0;
}
}
}
EntityFrameworkUnitOfWork
(或任何
IUnitOfWork
)可以根据需要执行
开始
提交
回滚
IUnitOfWork
还实现了
IDisposable
,以确保底层事务对象得到清理。使用相同的上下文还可以确保事务一定会应用于使用该上下文的存储库

此外,如果有检查以确保一次只打开一个事务,则可以传入
IUnitOfWork
而不是factory;但是,为了消除与实现的耦合,我们创建了一个工厂。这不仅确保了使用
块时每个
只有一个事务,而且我们还可以使用
块进行
,而不必触碰消费代码中的
i单元
的构造函数

作为免责声明,我衷心同意您不应该在存储库中包装ORM。它会弄乱你的数据库代码,增加不必要的复杂性。我们使用这些存储库是为了在做简单的事情时使我们的数据库交互不可知。否则,我们在存储库实现中会变得更加具体,从而消除了其他存储库模式实现所遭受的许多魔力。通常,一旦方法超出了单个记录操作的范围,数据库引擎和驱动程序就会对如何进行有不同的想法

最后一点注意:很明显,如果您注入的存储库与您注入的上下文不匹配(例如,
IMongoContext
IEntityFrameworkRepository
),您的代码将不会在与数据库的事务中运行。这不令人担忧的原因是

  • 在大多数情况下,使用与存储库中不同的上下文已经是毫无意义的了
  • 您必须在使用代码中管理上下文和存储库,并且
    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;
        }
    }
    
    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;
            }
        }
    }