我是否需要为EF Core提供UnitOfWork?如果有的话,是否有某个标准模式?

时间:2017-03-28 17:44:44

标签: asp.net-core-mvc entity-framework-core unit-of-work

我在某些地方读过EF已经实现了它自己的UnitOfWork和事务。

我正在寻找以下解决方案: https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

我真的不喜欢这个,因为我不想手动添加我拥有的每种类型的回购,因为这违背了GenericRepository的原因,我已经把它做了很多工作通用已经。

同时查看此处描述的UnitOfWork属性解决方案,但由于作者自己讨论的原因而退出: Entity Framework Core 1.0 unit of work with Asp.Net Core middleware or Mvc filter

但是,让我试着在下面的讨论中提出我的问题。

我有通用回购和通用服务。它们在我的Startup中注册如下:

services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));
services.AddScoped(typeof(IGenericService<>), typeof(GenericService<>));

My Generic Repo看起来像这样(为了简洁而省略了接口):

public enum FilteredSource
{
    All,
    GetAllIncluding,
}

public class GenericRepository<T> : IGenericRepository<T>
    where T: BaseEntity
{
    protected readonly ApplicationDbContext _context;
    protected DbSet<T> _dbSet;

    public GenericRepository(ApplicationDbContext context)
    {
        _context = context;
        _dbSet = context.Set<T>();
    }

    // no eager loading
    private IQueryable<T> All => _dbSet.Cast<T>();

    // eager loading
    private IQueryable<T> GetAllIncluding(
        params Expression<Func<T, object>>[] includeProperties) =>
         includeProperties.Aggregate(All, (currentEntity, includeProperty) => currentEntity.Include(includeProperty));

    // eager loading
    public async Task<T> GetSingleIncludingAsync(
        long id, params Expression<Func<T, object>>[] includeProperties)
    {
        IQueryable<T> entities = GetAllIncluding(includeProperties);
        //return await Filter<long>(entities, x => x.Id, id).FirstOrDefaultAsync();
        return await entities.SingleOrDefaultAsync(e => e.Id == id);
    }

    // no eager loading
    public async Task<T> GetSingleIncludingAsync(long id)
    {
        return await _dbSet.SingleOrDefaultAsync(e => e.Id == id);
    }

    /// <summary>
    /// Takes in a lambda selector and let's you filter results from GetAllIncluding or All.
    /// </summary>
    /// <param name="selector">labmda expression to filter results by.</param>
    /// <param name="getFilteredSource">All or GetAllIncluding as the method to get results from.</param>
    /// <param name="includeProperties">array of eager load lamda expressions.</param>
    /// <returns></returns>
    public async Task<IEnumerable<T>> GetFiltered(
        Expression<Func<T, bool>> selector, FilteredSource filteredSource,
        Expression<Func<T, object>>[] includeProperties = null)
    {
        var results = default(IEnumerable<T>);
        switch (filteredSource)
        {
            case FilteredSource.All:
                results = All.Where(selector);
                break;
            case FilteredSource.GetAllIncluding:
                results = GetAllIncluding(includeProperties).Where(selector);
                break;
        }
        return await results.AsQueryable().ToListAsync();
    }

    public async Task<IEnumerable<T>> GetUnFiltered(
        FilteredSource filteredSource,
        Expression<Func<T, object>>[] includeProperties = null)
    {
        var results = default(IEnumerable<T>);
        switch (filteredSource)
        {
            case FilteredSource.All:
                results = All;
                break;
            case FilteredSource.GetAllIncluding:
                results = GetAllIncluding(includeProperties);
                break;
        }
        return await results.AsQueryable().ToListAsync();
    }

    public async Task<T> InsertAsync(T entity)
    {
        if (entity == null)
        {
            throw new ArgumentNullException($"No {nameof(T)}  Entity was provided for Insert");
        }
        await _dbSet.AddAsync(entity);
        return entity;
    }

    public async Task<T> UpdateAsync(T entity)
    {
        T entityToUpdate = await
            _dbSet.AsNoTracking().SingleOrDefaultAsync(e => e.Id == entity.Id);
        if (entityToUpdate == null)
        {
            //return null;
            throw new ArgumentNullException($"No {nameof(T)}  Entity was provided for Update");
        }

        _dbSet.Update(entity);
        return entity;
    }

    public async Task<T> DeleteAsync(T entity)
    {
        _dbSet.Remove(entity);
        return await Task.FromResult(entity);
    }

    public Task SaveAsync() => _context.SaveChangesAsync();

}

服务层看起来像这样:

public class GenericService<T> : IGenericService<T>
    where T : BaseEntity
{
    private IGenericRepository<T> _genericRepo;

    public GenericService(IGenericRepository<T> genericRepo)
    {
        _genericRepo = genericRepo;
    }

    public async Task<IEnumerable<T>> GetFiltered(
        Expression<Func<T, bool>> selector, FilteredSource filteredSource,
        Expression<Func<T, object>>[] includeProperties = null)
    {
        return await _genericRepo.GetFiltered(selector, filteredSource,
            includeProperties);
    }

    public async Task<IEnumerable<T>> GetUnFiltered(
        FilteredSource filteredSource,
        Expression<Func<T, object>>[] includeProperties = null)
    {
        return await _genericRepo.GetUnFiltered(filteredSource,
            includeProperties);
    }

    // no eager loading
    public async Task<T> GetSingleIncludingAsync(long id)
    {
        return await _genericRepo.GetSingleIncludingAsync(id);
    }
    // eager loading
    public async Task<T> GetSingleIncludingAsync(long id, params Expression<Func<T, object>>[] includeProperties)
    {
        T entity = await _genericRepo.GetSingleIncludingAsync(id, includeProperties);
        //return await Filter<long>(entities, x => x.Id, id).FirstOrDefaultAsync();
        return entity;
    }

    public async Task<T> InsertAsync(T entity)
    {
        var result = await _genericRepo.InsertAsync(entity);
        await _genericRepo.SaveAsync();
        return entity;
    }

    public async Task<T> UpdateAsync(T entity)
    {
        var result = await _genericRepo.UpdateAsync(entity);
        if (result != null)
        {
            await _genericRepo.SaveAsync();
        }
        return result;
    }

    public async Task<T> DeleteAsync(T entity)
    {
        throw new NotImplementedException();
    }
}

使用该服务的MVC Core Web API控制器的示例如下所示:

[Route("api/[controller]")]
public class EmployeesController : Controller
{
    private IGenericService<Employee> _genericService;

    public EmployeesController(IGenericService<Employee> genericService)
    {
        _genericService = genericService;
    }

    // GET: api/employees
    [HttpGet]
    public async Task<IEnumerable<Employee>> GetEmployeesAsync(
            string firstName = null, string lastName = null)
    {
        return await _genericService.GetFiltered(
                e => (string.IsNullOrEmpty(firstName) || e.FirstName.Contains(firstName))
                && (string.IsNullOrEmpty(lastName) || e.LastName.Contains(lastName)),
                FilteredSource.GetAllIncluding,
                new Expression<Func<Employee, object>>[] { a => a.Organization,
                b => b.PayPlan,
                c => c.GradeRank,
                d => d.PositionTitle,
                e => e.Series,
                f => f.BargainingUnit }
            );
    }

    // GET api/employees/5
    [HttpGet("{id}", Name = "GetEmployeeById")]
    public async Task<IActionResult> GetEmployeeByIdAsync(long id)
    {
        var employee = await _genericService.GetSingleIncludingAsync(id,
            a => a.Organization,
            b => b.PayPlan,
            c => c.GradeRank,
            d => d.PositionTitle,
            e => e.Series,
            f => f.BargainingUnit);

        if (employee == null)
        {
            return NotFound();
        }
        else
        {
            return new ObjectResult(employee);
        }
    }

    // PUT api/employees/id
    [HttpPut("{id}")]
    public async Task<IActionResult> PutEmployeeAsync([FromBody] Employee emp)
    {
        var employee = await _genericService.UpdateAsync(emp);
        if (employee == null)
        {
            return NotFound();
        }

        return new ObjectResult(employee);
    }
} 

所以这是我的问题: 我理解它的方式,UnitOfWork用于您将两个存储库引入服务或控制器。如果您操作repo 1中的数据并且它通过,然后操作repo 2中的数据并且它失败(或反之亦然)您想要回滚所有内容。

到目前为止,我没有使用两个回购。但是如果发生这种情况,那将是因为我将两个GenericServices引入一个控制器,这将带来两个GenericRepos。

现在让我们谈谈范围。 我必须将我的GenericRepo作为Scoped引入。不是单身人士。因为如果我在一个请求上查询一个对象,然后尝试在下一个请求中更新该对象,我将得到一个错误,该对象无法更新,因为它已经被上一个请求中的Singleton Repo跟踪。 所以我把它带到了Scoped中,如StartUp片段所示:

services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));
services.AddScoped(typeof(IGenericService<>), typeof(GenericService<>));

我还将服务作为范围引入。几乎猜测我应该把它带入范围。

现在,如果我引入类型员工的GenericService - &gt;它将Type Employee的GenericRepository导入控制器,并且我还引入了Type Case的GenericService - &gt;得到Type Case的GenericRepository,这两个不同的GenericRepos?或者他们是相同的回购? 他们会被视为同一笔交易并且全部通过或失败吗?

或者我是否需要手动实施UnitOfWork?

我认为其中的另一个因素是Core DI行中的以下内容是Singleton还是Scoped:

services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyConn")));

1 个答案:

答案 0 :(得分:0)

您使用Scoped是正确的,但请参见下文:您的ApplicationDbContext必须是作用域,服务和回购可能是暂时的。

由于IGenericRepo<Employee>IGenericRepo<Case>的类型不同,是的,您将获得两个不同的GenericRepo<T>(并且相同的逻辑适用于服务)。

但是关于你链接的文章中的UnitOfWork模式,我没有得到你的评论,我不喜欢这样,因为我不想手动添加在每种类型的回购中......&#34;

您可以将文章中的UoW代码更改为GenericUnitOfWork<T1,T2>

或者你可以认为,对于需要写入2个repos的每个控制器,你专门为它编写一个UoW类。请注意,您可以删除文章在其UoW类中使用的惰性getter代码,因为服务容器已经为您创建了repos,因此UoW只减少到几行主要是锅炉代码。

public class UnitOfWork<T1,T2> : IDisposable 
{
    ApplicationDbContext context;
    GenericRepo<T1> Repo1 {get; private set;}
    GenericRepo<T2> Repo2 {get; private set;}

    public UnitOfWork(ApplicationDbContext context)
    {
      this.context=context;
      Repo1 =new GenericRepo<T1>(context)
      Repo2 =new GenericRepo<T2>(context)
    }

    public void Save()
    {
        context.SaveChanges();
    }

    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                context.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

但重要的是:文章中提到的UoW模式主要取决于使用相同 DbContext的repos(否则它将是分布式事务和需要更多代码)

因此,ApplicationDbContext的作用域非常重要:

services.AddScoped(typeof(ApplicationDbContext), typeof(ApplicationDbContext));

但是,您必须确保应用程序中的每个控制器都能够愉快地接受这个限制,它只需要一个基础ApplicationDbContext。对于必须的情况,它应该没问题。

最后,你也可以明确指出真正的依赖是DbContext

public class EmployeesController : Controller
{
  UnitofWork<T1,T2> uow;

  public EmployeesController(ApplicationDbContext dbContext)
  {
    this.uow= new UnitofWork<Employee,Case>(dbContext)
  }
//...