2017-03-28 3 views
1

Ich habe an einigen Stellen gelesen, dass EF bereits eigene UnitOfWork und Transaktionen implementiert.Muss ich UnitOfWork für EF Core übernehmen und wenn ja, gibt es irgendwo ein Standardmuster?

ich bei den folgenden Lösungen suchen: 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

Ich weiß wirklich nicht, wie das, weil ich möchte nicht hinzufügen müssen manuell in jede Art von Repo-I wie haben sich gegen den Grund das GenericRepository, an das ich bereits viel gearbeitet habe.

Suche Auch bei der UnitOfWork Attribute hier beschriebenen Lösung aber wegen der Gründe, vom Autor selbst diskutierten die Sicherung weg: Entity Framework Core 1.0 unit of work with Asp.Net Core middleware or Mvc filter

Aber lassen Sie mich versuchen, unter meiner Frage in der Diskussion um das Layout.

Ich habe einen Generic Repo und einen Generic Service. Sie sind in meinem Startup wie folgt registriert:

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

Mein Allgemein Repo sieht wie folgt aus (Schnittstellen zur Kürze Weglassen):

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(); 

}

Service Layer sieht wie folgt aus:

Ein Beispiel für einen MVC Core Web API-Controller, der den Dienst verwendet, sieht folgendermaßen aus:

[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); 
    } 
} 

hier sind also meine Fragen: So wie ich es verstehe, UnitOfWork verwendet wird, falls Sie in zwei Repositorys in einen Dienst oder Controller bringen. Wenn Sie Daten in Repo 1 manipulieren und es passiert und dann Daten in Repo 2 manipuliert und es fehlschlägt (oder umgekehrt), möchten Sie alles zurückrollen.

Bis jetzt verwende ich nicht zwei Repos. Aber wenn diese Situation auftritt, wird es sein, weil ich zwei GenericServices zu einem Controller bringe, der wiederum zwei GenericRepos einbringt.

Jetzt wollen wir über Umfang sprechen. Ich muss meine GenericRepo als Scoped einbringen. Nicht Singleton. Wenn ich ein Objekt bei einer Anfrage abfrage und dann versuche, dieses Objekt bei der nächsten Anfrage zu aktualisieren, erhalte ich den Fehler, dass das Objekt nicht aktualisiert werden kann, weil es bereits von der vorherigen Anfrage vom Singleton Repo verfolgt wird. So bringe ich es in Scoped wie im StartUp-Segment gezeigt:

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

ich als scoped auch in den Dienst bringen. Ziemlich viel raten sollte ich es auch als Scoped einbringen.

Wenn ich jetzt einen GenericService vom Typ Employee -> der ein GenericRepository vom Typ Employee in einen Controller bringt und ich auch GenericService vom Typ Case einbringe -> welches ein GenericRepository vom Type Case bekommt, sind diese beiden verschieden GenerischeRepos? Oder sind sie das gleiche Repo? Werden sie als dieselbe Transaktion behandelt und alle bestehen oder versagen?

Oder muss ich UnitOfWork manuell implementieren?

Ein weiterer Faktor, ich denke, dass geht in diese, ob die in Core-DI Linie gebacken wird nach Singleton oder Scoped:

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

Wie ich Ihre Frage, meinen Standpunkt zu verstehen ist: Nein, Sie brauchen nicht zu UOW auf implementieren. NET Core, weil es besser ist, die Controller und Business-Objekte oder Repositories nach Features zu gestalten: HumanResources statt Employee, Sales statt Order. Auch Uow ist ein Objekt, das Änderungen erlaubt. Wenn Sie also Ihre Repositories haben, können Sie Änderungen im Action Controller festschreiben –

+0

Im Allgemeinen sind Singletons böse, besonders in einer Webanwendung. https://duckduckgo.com/singletonitis –

Antwort

0

Sie haben Recht Scoped zu benutzen, aber unten zu sehen: es ist Ihr ApplicationDbContext dass scoped werden müssen , die Dienste und Repos können vorübergehend sein.

Da IGenericRepo<Employee> ist eine andere Art zu IGenericRepo<Case> ja zwei verschiedene GenericRepo<T> ‚s erhalten wird (und die gleiche Logik auf die Dienste gilt).

Aber über das UnitOfWork-Muster in dem Artikel, den Sie verknüpfen, bekomme ich Ihren Kommentar nicht, dass "Ich mag das nicht wirklich, weil ich nicht in jede Art von Repo manuell hinzufügen möchten ... "

Sie könnten den UoW Code in dem Artikel zu einem GenericUnitOfWork<T1,T2> ändern?

Oder Sie könnten der Ansicht sein, dass für jeden Controller, der auf 2 Repos schreiben muss, Sie eine UoW Klasse speziell dafür schreiben. Beachten Sie, dass Sie den Lazy-Getter-Code, den der Artikel in seiner Klasse UoW verwendet, entfernen können, da Ihre Repos ohnehin für Sie bereits vom Services-Container erstellt werden, sodass sich die UoW auf nur einige wenige Zeilen des Mostly-Boiler-Codes reduziert.

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); 
    } 
} 

Aber hier ist das Wichtigste: die UOW Muster wie in dem Artikel dargestellt, hängt entscheidend von den beiden repos die gleichenDbContext mit (sonst wäre es eine verteilte Transaktion sein und erfordert mehr Code)

So es ist wichtig, dass Ihr ApplicationDbContext scoped ist:

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

aber Sie müssen sicher sein, dass jeder Controller in Ihrer Anwendung dieses glücklich akzeptieren Einschränkung, dass es nur einen zugrunde liegenden ApplicationDbContext benötigen sollte. Für Must-Fälle sollte es in Ordnung sein.

Schließlich Sie auch explizit sein könnte, dass die reale Abhängigkeit der DbContext:

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

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