如何使用EF仓储(二)-UnitOfWork

前言

本文主要介绍事务与工作单元,仓储的基础方法使用请查看如何使用仓储(一)-基础功能
22.Adnc.Infr.EfCore是仓储的实现工程,集成了EFcore作为仓储的实现。

EFCore的事务

EFCore的事务分为以下三种

  • SaveChanges
  • DbContextTransaction
  • TransactionScope

EFCore默认情况下,是开启了SaveChanges事务的。然而基于以下原因Adnc关闭了SaveChanges自动事务。

  1. UpdateRangeAsync,DeleteRangeAsync 批量删除,批量更新方法不是走EfCore的原生方法,不受SaveChanges事务控制。
  2. Cap的事务也不受SaveChanges事务控制
  3. 最后一点就是与读写分离相关,请参考如何实现读写分离
public class AdncDbContext : DbContext
{
     public AdncDbContext()
     {
         Database.AutoTransactionsEnabled = false;
     }
}

在关闭SaveChanges自动事务后,Adnc通过DbContextTransaction统一控制事务。如果你的业务逻辑调用增/删/改的方法>=2次,就需要显示声明拦截器来开启事务控制。

主从表插入、主从表更新、批量新增、批量修改、批量删除操作都可以通过一个增/删/改的方法实现,并不需要显示开启事务。

22.Adnc.Infr.EfCore实现了03.Adnc.Core.SharedIUnitOfWork接口,并且在AdncInfrEfCoreModule.cs文件中已经注册。工作单元拦截器在03.Adnc.Core.Shared 工程的 Interceptors目录中,都已经实现好。我们只需要在使用的地方注入拦截器,然后显示声明。

如何使用

注册

在Application(AdncxxxApplicationModule.cs)或者Core(AdncxxxCoreModule.cs)工程注册拦截器。

如果采用经典三层开发模式,在Application或者Core层注册都可以。
如果采用DDD开发模式,则只能在Application层注册。

在经典三层开发模式中,Adnc是在Core层注册的,主要还是基于灵活控制读写分离的目的。如果在Core层使用工作单元来控制事务,意味着你需要多写一些一点代码。对于经典三层开发模式来说,在Application或者Core层注册,都没有问题,取决于你自己的考虑。

public class AdncCusCoreModule : Module
{
    /// <summary>
    /// Autofac注册
    /// </summary>
    /// <param name="builder"></param>
    protected override void Load(ContainerBuilder builder)
    {
        //注册其它组件
        // todo
        //注册事务拦截器
        builder.RegisterType<UowInterceptor>()
                .InstancePerLifetimeScope();
        builder.RegisterType<UowAsyncInterceptor>()
                .InstancePerLifetimeScope();

        //注册Core服务
        builder.RegisterAssemblyTypes(this.ThisAssembly)
            .Where(t => t.IsAssignableTo<ICoreService>())
            .AsSelf()
            .InstancePerLifetimeScope()
            .EnableClassInterceptors()
            .InterceptedBy(typeof(UowInterceptor));
    }
}

显示声明

如果方法中需要用到Cap发布事件,需要使用[UnitOfWork(SharedToCap = true)]声明。

[UnitOfWork]
public virtual async Task ProcessRechargingAsync(long transactionLogId, long customerId, decimal amount)
{
    var transLog = await _cusTransactionLogRepo.FindAsync(transactionLogId, noTracking: false);
    if (transLog == null || transLog.ExchageStatus != ExchageStatusEnum.Processing)
        return;

    var finance = await _cusFinaceRepo.FindAsync(customerId, noTracking: false);
    var originalBalance = finance.Balance;
    var newBalance = originalBalance + amount;

    finance.Balance = newBalance;
    await _cusFinaceRepo.UpdateAsync(finance);

    transLog.ExchageStatus = ExchageStatusEnum.Finished;
    transLog.ChangingAmount = originalBalance;
    transLog.ChangedAmount = newBalance;
    await _cusTransactionLogRepo.UpdateAsync(transLog);
}

[UnitOfWork(SharedToCap = true)]
public virtual async Task RechargeAsync(CustomerTransactionLog cusTransactionLog, CancellationToken cancellationToken = default)
{
    await _cusTransactionLogRepo.InsertAsync(cusTransactionLog);

    //发布充值事件
    var eventId = IdGenerater.GetNextId(IdGenerater.DatacenterId, IdGenerater.WorkerId);
    var eventData = new CustomerRechargedEvent.EventData() { CustomerId = cusTransactionLog.CustomerId, TransactionLogId = cusTransactionLog.Id, Amount = cusTransactionLog.Amount };
    var eventSource = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName;
    await _eventPublisher.PublishAsync(new CustomerRechargedEvent(eventId, eventData, eventSource));
}

https://aspdotnetcore.net/ef-gurd/
https://aspdotnetcore.net/ef-unitofwork/
https://aspdotnetcore.net/ef-codefirst/
https://aspdotnetcore.net/ef-core-readwrite/