我是否需要为 EF Core 暗示工作单元,如果是,是否有标准模式?



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

我正在寻找以下解决方案: https://learn.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 属性解决方案,但由于作者自己讨论的原因而退出: 具有 Asp.Net 核心中间件或 Mvc 筛选器的实体框架核心 1.0 工作单元

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

我有一个通用存储库和一个通用服务。 它们在我的创业公司中注册如下:

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

我的通用存储库如下所示(为简洁起见,省略了接口):

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用于将两个存储库引入服务或控制器的情况。 如果在存储库 1 中操作数据并且它通过,然后在存储库 2 中操作数据并且失败(反之亦然),则需要回滚所有内容。

到目前为止,我没有使用两个存储库。但是,如果发生这种情况,那是因为我将两个泛型服务引入控制器,而控制器又会引入两个泛型存储库。

现在让我们谈谈范围。 我必须将我的泛型存储库作为范围引入。不是单例。 因为如果我在一个请求上查询一个对象,然后尝试在下一个请求上更新该对象,我将收到一个错误,指出该对象无法更新,因为它已被上一个请求中的单一实例存储库跟踪。 因此,我将其引入范围,如启动部分所示:

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

我还引入了范围内的服务。 几乎猜测我也应该把它带进来。

现在,如果我引入 employee 类型的 geneericService -> 将 Employee 类型的 GenericRepository 放入控制器,并且我还引入 Type Case -> 的 GenericService 得到 Case 类型的 GenericRepository,这两个是不同的 GenericRepos? 还是它们是同一个回购? 它们会被视为同一笔交易,并且一起通过还是失败?

还是需要手动实现工作单元?

我认为另一个因素是以下融入Core DI行的是单例还是作用域:

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

使用 Scoped 是正确的,但请参阅下文:必须限定范围的是你的ApplicationDbContext,服务和存储库可以是暂时的。

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

但是关于您链接的文章中的 UnitOfWork 模式,我没有得到您的评论"我真的不喜欢这样,因为我不想手动添加每种类型的存储库......">

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

或者你可以认为,对于每个需要写入 2 个存储库的控制器,你专门为它编写一个UoW类。请注意,您可以去除文章在其UoW类中使用的惰性 getter 代码,因为您的存储库已经由服务容器为您创建,因此无论如何,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的两个存储库(否则它将是一个分布式事务并需要更多代码)

因此,您的 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)
}
//...

相关内容

最新更新