2017-03-28 75 views
1

我已經在一些地方看到EF已經實現了它自己的UnitOfWork和事務。我是否需要爲EF Core實現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

但讓我嘗試奠定了我在下面的討論問題。

我有一個通用回購和一個通用服務。他們註冊在我的啓動是這樣的:

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中處理數據,並且它失敗(或反之亦然),則要將所有回滾。

到目前爲止,我沒有使用兩個回購。但是,如果出現這種情況,那將是因爲我將兩個GenericServices引入控制器,而這又會帶來兩個GenericRepos。

現在我們來談談範圍。 我必須把我的GenericRepo作爲Scoped。不是單身人士。因爲如果我在一個請求中查詢一個對象,然後嘗試在下一個請求中更新該對象,那麼我將得到一個錯誤,指出該對象無法更新,因爲它已經由前一個請求中的Singleton Repo跟蹤。 所以我把它的作用域如圖啓動段:

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

我也帶來了服務的範圍的。相當多的猜測,我應該把它納入範圍之內。

現在,如果我引入類型Employee的GenericService - >將類型Employee的GenericRepository獲取到控制器中,並且還引入Type Case的GenericService - >其獲取Type Case的GenericRepository,這兩個不同GenericRepos?或者它們是相同的回購? 它們會被視爲同一事務,並且一起通過或失敗嗎?

還是我需要手動實現UnitOfWork?

另一個因素我認爲,進入這個判斷下列烤成核心DI線單身或作用域:

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

正如我明白你的問題,我的觀點是:不,你不需要落實UOW。因爲最好根據特性設計控制器和業務對象或存儲庫:HumanResources而不是Employee,Sales而不是Order。此外,Uow是允許提交更改的對象,因此如果您擁有自己的存儲庫,則可以在操作控制器中落實更改 –

+0

一般來說,單身人士是邪惡的,特別是在Web應用程序中。 https://duckduckgo.com/singletonitis –

回答

0

您是使用權作用域但請看下圖:這是你的ApplicationDbContext必須作用域,服務和回購可能是暫時的。

由於IGenericRepo<Employee>是與IGenericRepo<Case>是不同的類型是的,你會得到兩個不同的GenericRepo<T>的(和相同的邏輯適用於服務)。

但是關於您鏈接的文章中的UnitOfWork模式,我沒有得到您的評論:「我不太喜歡這個,因爲我不想手動添加每種類型的回購。 「

您可以將文章中的UoW代碼更改爲GenericUnitOfWork<T1,T2>

或者您可以認爲,對於需要寫入2個倉庫的每個控制器,您專門爲其編寫了一個UoW類。請注意,您可以刪除文章在其UoW類中使用的懶惰吸氣劑代碼,因爲您的回購已由服務容器爲您創建,因此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) 
    } 
//... 
相關問題