我在某些地方读过EF已经实现了它自己的UnitOfWork和事务。
我真的不喜欢这个,因为我不想手动添加我拥有的每种类型的回购,因为这违背了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")));
答案 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)
}
//...