2016-08-09 12 views
2

Ich versuche, ein minimales generisches Repository-Muster in meiner Anwendung zu implementieren. Ich habe eine wirklich kleine Schnittstelle zum Abfragen und Speichern von Daten:Minimale Repository-Implementierung mit Entity Framework

public interface IRepository 
{ 
    IQueryable<TEntity> Query<TEntity>() 
     where TEntity: BaseEntity; 

    void Save<TEntity>(TEntity entity) 
     where TEntity : BaseEntity; 
} 

BaseEntity ist eine Basisklasse für alle Objekte, die ich in meinem Repository gespeichert werden:

public abstract class BaseEntity 
{ 
    public Guid Id { get; set; }  
    public DateTime CreatedDate { get; set; } 
    public DateTime UpdatedDate { get; set; } 
} 

Ich habe versucht, eine funktionierende Implementierung zu finden von solch einem einfachen Repository mit Entity Framework, aber es war überraschend schwer zu finden (Leute verwenden UnitOfWork und andere Dinge, die die Implementierung komplexer als ich will).

Also habe ich die absolut minimale Implementierung ich einfiel:

public class EfRepository : DbContext, IRepository 
{ 
    public IQueryable<TEntity> Query<TEntity>() where TEntity : BaseEntity 
    { 
     return this.Set<TEntity>(); 
    } 

    public void Save<TEntity>(TEntity entity) where TEntity : BaseEntity 
    { 
     if (entity.Id == default(Guid)) 
     { 
      entity.Id = Guid.NewGuid(); 
      this.Set<TEntity>().Add(entity); 
     } 
     else 
     { 
      this.Entry(entity).State = EntityState.Modified; 
     }  

     this.SaveChanges(); 
    } 

    public DbSet<User> Users { get; set; } // User is a subclass of BaseEntity 
    //Other DbSet's... 
} 

Jetzt ist meine Frage, ob eine solche Implementierung korrekt ist. Ich frage, weil ich mit Entity Framework neu bin und ich mache mir Sorgen über mögliche Leistungsprobleme oder Dinge, die möglicherweise bei der Verwendung eines solchen Repositories schief gehen könnten.

Hinweis: Ich versuche, all dies aus 2 Gründen zu tun:

  • Für Testzwecke, so dass ich ein Modell des Endlagers in meinen Unit-Tests erstellen kann Projekte
  • Es ist möglich, dass ich Ich werde in Zukunft zu einem anderen ORM wechseln müssen, und ich möchte diesen Übergang so einfach wie möglich machen.
+0

Es kann sich lohnen zu überlegen, ob EF das richtige ORM ist, wenn Sie nach einer leichten Berührung Probleme mit der Leistung haben.Aus persönlicher Erfahrung habe ich nicht gefunden, dass EF in irgendeinem der Projekte, in denen ich es benutzt habe, sehr performant ist, und es ist ein wenig klobig, seine Zuordnungen in Übereinstimmung mit der DB oder dem Code zu halten (abhängig davon, welchen Pfad du wählst, Code oder DB zuerst). Jeder, den ich kenne, der in Umgebungen mit hohem Durchsatz arbeitet, krümmt sich physisch, wenn jemand EF erwähnt. –

+5

Die Frage ist wirklich: seit EF * bereits * implementiert das Repository ('DbSet ') und Unit-of-Work ('DbContext') Muster - warum neu erfinden das Rad mit noch einer anderen Schicht darüber? –

+0

@marc_s, ich habe 2 Gründe :) Bitte, siehe meine edit –

Antwort

2

Zunächst sind die Repositories umstritten. Es gibt viele Leute, die stark dagegen sind und viele es aus verschiedenen Gründen benutzen (oder daran gewöhnt sind). Es gibt viele Artikel über das Internet mit endlosen Diskussionen über Vor- und Nachteile. Es liegt an Ihnen zu entscheiden, ob Sie tatsächlich das Repository-Muster in Ihrem Projekt benötigen - konzentrieren wir uns nicht darauf, wie Sie gefragt haben, "Wie geht das in C#?" nicht "sollte ich das tun?".

Ihre Repository-Implementierung erweitert sich um DbContext. Dies bedeutet, dass Sie eine Transaktion, die mehr als ein Repository (mehr als einen Entitätstyp) umfasst, nicht effektiv erstellen können, da jedes Repository seine eigene DbContext hat (wie es der Kontext ist). Unter der Haube verfolgt DbContext Änderungen an den Entitäten. Wenn Sie mehr als einen Kontext haben und versuchen, beide gleichzeitig zu speichern, wissen sie nicht voneinander. Das bringt uns ein Problem - wenn der erste Aufruf von SaveChanges() erfolgreich ist und zweitens fehlschlägt, wie man den ersten zurücksetzt? Es wurde bereits gespeichert? Dort erscheint die Arbeitseinheit.

So müssen Sie zunächst ein Repository-Schnittstelle - als eine Sammlung von Entitäten handeln:

public interface IRepository<TEntity> 
{ 
    TEntity Get(Expression<Func<TEntity, bool>> predicate); 
    IEnumerable<TEntity> GetAll(); 
    IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> predicate); 

    void Add(TEntity entity); 
    void AddAll(IEnumerable<TEntity> entities); 

    void Remove(TEntity entity); 
    void RemoveAll(IEnumerable<TEntity> entities); 
} 

Und Arbeitseinheit:

public interface IUnitOfWork : IDisposable 
{ 
    // Commit all the changes 
    void Complete(); 

    // Concrete implementation -> IRepository<Foo> 
    // Add all your repositories here: 
    IFooRepository Foos {get;} 
} 

Basisklassen könnte wie folgt aussehen:

public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class 
{ 
    protected DbContext Context { get; private set; } 

    public BaseRepository(DbContext dbContext) 
    { 
     Context = dbContext; 
    } 

    public virtual TEntity Get(Expression<Func<TEntity, bool>> predicate) 
    { 
     return Context.Set<TEntity>().Where(predicate).FirstOrDefault(); 
    } 

    public virtual IEnumerable<TEntity> GetAll() 
    { 
     return Context.Set<TEntity>().ToList(); 
    } 

    public virtual IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> predicate) 
    { 
     return Context.Set<TEntity>().Where(predicate).ToList(); 
    } 

    public void Add(TEntity entity) 
    { 
     var entry = Context.Entry(entity); 
     if(entry.State == EntityState.Detached) 
     { 
      Context.Set<TEntity>().Add(entity); 
     } 
     else 
     { 
      entry.State = EntityState.Modified; 
     } 
    } 

    public void AddAll(IEnumerable<TEntity> entities) 
    { 
     foreach(var entity in entities) 
     { 
      Add(entity); 
     } 
    } 

    public void Remove(TEntity entity) 
    { 
     var entry = Context.Entry(entity); 
     if (entry.State == EntityState.Detached) 
     { 
      Context.Set<TEntity>().Attach(entity); 
     } 
     Context.Entry<TEntity>(entity).State = EntityState.Deleted; 
    } 

    public void RemoveAll(IEnumerable<TEntity> entities) 
    { 
     foreach (var entity in entities) 
     { 
      Remove(entity); 
     } 
    } 

} 

Und Einheit der Arbeit Umsetzung:

public class UnitOfWork : IUnitOfWork 
{ 
    private readonly ApplicationDbContext _dbContext; 
    private IFooRepository _fooRepo; 

    public UnitOfWork(ApplicationDbContext dbContext) 
    { 
     _dbContext = dbContext; 

     // Each repo will share the db context: 
     _fooRepo = new FooRepository(_dbContext); 
    } 


    public IFooRepository Foos 
    { 
     get 
     { 
      return _fooRepo; 
     } 
    } 

    public void Complete() 
    { 
     _dbContext.SaveChanges(); 
    } 

    public void Dispose() 
    { 
     _dbContext.Dispose(); 
    } 
} 
+0

_Sie können nicht effektiv eine Transaktion erstellen, die mehr als ein repository_ umfasst - das ist nicht ganz wahr. Ich kann ein TransactionScope erstellen und es um alle notwendigen Repository-Aufrufe wickeln. Standardmäßig (ohne den Transaktionsbereich) schreibt meine Repository-Implementierung alle Änderungen mit jedem Aufruf von 'Save' fest, und dieses Standardverhalten ist in den meisten Fällen erforderlich. –

+0

Ihr 'Save()' wird alle modifizierten Entitäten aus allen Repositorys mit dem gleichen 'DbContext' einbinden, unabhängig davon, in welchem ​​Repo Sie es nennen werden. –