【问题标题】:How to handle database transactions in Generic Repository?如何处理通用存储库中的数据库事务?
【发布时间】:2020-05-17 22:04:44
【问题描述】:

我正在使用 ASP.NET Core WebAPIEFCore 3。 如何处理通用存储库方法中的事务?我知道存储库模式的优缺点,以及它的通用存储库方法,但让我感到困惑的是如何在使用它们时处理事务??

下面是我使用的BaseRepository。我在执行这样的事务时遇到问题:

  1. 添加Employee
  2. 创建EmployeeId
  3. Employee 的地址添加到刚刚创建的EmployeeId

我遇到的问题是我必须调用SaveChanges 来获取自动生成的员工 ID,以便能够插入员工的地址。 SaveChanges 提交事务(由 EF Core 创建)。 在使用BaseRepository 时,如何在事务中执行此操作?我是否应该在 BaseRepository 中公开 BeginTransactionCommit 方法,以便 Service 类可以创建事务并关闭它?

    public abstract class BaseRepository<TEntity> : IRepository<TEntity> 
        where TEntity : class, IEntity
    {
        private readonly DemoDb_context _context;

        public BaseRepository(DemoDb_context context)
        {
            _context = context;
        }

        public async Task<TEntity> GetById(int id)
        {
            return await _context.Set<TEntity>().FindAsync(id);
        }

        public async Task<List<TEntity>> GetAll()
        {
            return await _context.Set<TEntity>().ToListAsync();
        }

        public async Task<TEntity> Add(TEntity entity)
        {
            _context.Set<TEntity>().Add(entity);
            await _context.SaveChangesAsync();
            return entity;
        }

        public async Task<TEntity> Delete(int id)
        {
            var entity = await _context.Set<TEntity>().FindAsync(id);
            if (entity == null)
            {
                return entity;
            }

            _context.Set<TEntity>().Remove(entity);
            await _context.SaveChangesAsync();

            return entity;
        }

        public async Task<TEntity> Update(TEntity entity)
        {
            _context.Entry(entity).State = EntityState.Modified;
            await _context.SaveChangesAsync();
            return entity;
        }
    }

这是我的服务类,我认为应该在其中添加事务:

public class EmployeeService
{
    private readonly IEmployeeRepository _employeeRepository;
    private readonly IAddressRepository _addressRepository;
    private readonly IMapper _mapper;

    public EmployeeService(IEmployeeRepository employeeRepository, IAddressRepository addressRepository, IMapper mapper)
    {
        _employeeRepository = employeeRepository;
        _addressRepository = addressRepository;
        _mapper = mapper;
    }

    public async Task<EmployeeDto> Add(EmployeeDto employeeDto)
    {
        // TODO: Start transaction here ???

        var employee = _mapper.Map<Employee>(employeeDto);
        var addedEmployee = await _employeeRepository.Add(employee);

        var employeeAddress = Generate_Employee_Address_Entity_With_EmployeeId(addedEmployee); 
        var addedAddress = await _addressRepository.Add(employeeAddress);

        var output = Generate_Employee_Dto(addedEmployee, addedAddress); 

        // TODO: Commit transaction here ???

        return outputs;
    }
}

【问题讨论】:

    标签: c# asp.net-core .net-core asp.net-core-webapi ef-core-3.0


    【解决方案1】:

    实体框架的优点之一是您可以避免在您向我们展示的那种基本案例场景中使用事务。我建议如下:

    1. 在 BaseRepository 中,从 Add、Update 和 Delete 方法中删除 SaveChangesAsync,并公开一个名为 SaveDbChangesAsync() 的方法。

        public abstract class BaseRepository<TEntity> : IRepository<TEntity> 
              where TEntity : class, IEntity
          {
              private readonly DemoDb_context _context;
      
              public BaseRepository(DemoDb_context context)
              {
                  _context = context;
              }
      
              public async Task<TEntity> GetById(int id)
              {
                  return await _context.Set<TEntity>().FindAsync(id);
              }
      
              public async Task<List<TEntity>> GetAll()
              {
                  return await _context.Set<TEntity>().ToListAsync();
              }
      
              public async Task<TEntity> Add(TEntity entity)
              {
                  _context.Set<TEntity>().Add(entity);
                  return entity;
              }
      
              public async Task<TEntity> Delete(int id)
              {
                  var entity = await _context.Set<TEntity>().FindAsync(id);
                  if (entity == null)
                  {
                      return entity;
                  }
      
                  _context.Set<TEntity>().Remove(entity);    
                  return entity;
              }
      
              public async Task<TEntity> Update(TEntity entity)
              {
                  _context.Entry(entity).State = EntityState.Modified;
                  return entity;
              }
      
              public async Task SaveDbChangesAsync()
              {
                   await _context.SaveChangesAsync();
              }
          }
      

    通过这种方式,您不会将更改存储到数据库中,而只会在上下文中的内存中跟踪更改。

    1. 完成所有逻辑后,您可以从服务类(通常是使用存储库的类)中调用 SaveDbChangesAsync() 方法并保留更改。

      public class EmployeeService
      {
          private readonly IEmployeeRepository _employeeRepository;
          private readonly IAddressRepository _addressRepository;
          private readonly IMapper _mapper;
      
          public EmployeeService(IEmployeeRepository employeeRepository, IAddressRepository addressRepository, IMapper mapper)
          {
              _employeeRepository = employeeRepository;
              _addressRepository = addressRepository;
              _mapper = mapper;
          }
      
          public async Task<EmployeeDto> Add(EmployeeDto employeeDto)
          {
              // TODO: Start transaction here ???
      
              var employee = _mapper.Map<Employee>(employeeDto);
              var addedEmployee = await _employeeRepository.Add(employee);
      
              var employeeAddress = Generate_Employee_Address_Entity_With_EmployeeId(addedEmployee); 
              var addedAddress = await _addressRepository.Add(employeeAddress);
      
              var output = Generate_Employee_Dto(addedEmployee, addedAddress); 
      
      
              //// Here instead of commit transaction, we save changes to
              //// the database. If anything goes wrong changes will be discarded 
              //// anyways when you context gets out of scope;
              await SaveDbChangesAsync();
      
              return outputs;
          }
      

    好处:

    1. 避免使用事务等性能要求高的操作
    2. 您以正确的方式使用 EF

    但是,如果您坚持使用交易,您可以执行以下操作:

    using (EntitiesContext context = new EntitiesContext())  
    {  
        using (var transaction = context.Database.BeginTransaction())  
        {  
        }
    }
    

    【讨论】:

    • 谢谢。纠正我,如果我错了,但我认为Add 方法(在存储库中)不会返回新员工的Id。因此,如果您没有员工 ID,如何将地址插入数据库?我的问题的重点是:我必须使用SaveChanges 才能获得自动生成的 ID,它将在地址表中使用。
    • 好的,现在我知道你想做的事了。您有两个选择:1)您将整个功能包装到一个事务中,如答案所示。 2)您可以使用SQL的序列概念,您可以创建主键,但它不再是自动增量。 docs.microsoft.com/en-us/sql/t-sql/statements/…
    猜你喜欢
    • 2019-01-16
    • 2016-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多