Ich habe meine Repository-Klassen implementiert, wie Sie unten sehen können
public Class MyRepository
{
private MyDbContext _context;
public MyRepository(MyDbContext context)
{
_context = context;
}
public Entity GetEntity(Guid id)
{
return _context.Entities.Find(id);
}
}
Ich habe jedoch kürzlich diesen Artikel gelesen, der besagt, dass es eine schlechte Praxis ist, Datenkontext als privates Mitglied in Ihrem Repository zu haben: http://devproconnections.com/development/solving-net-scalability-problem
Theoretisch ist der Artikel nun richtig: Da DbContext IDisposable implementiert, ist die korrekteste Implementierung die folgende.
public Class MyRepository
{
public Entity GetEntity(Guid id)
{
using (MyDbContext context = new MyDBContext())
{
return context.Entities.Find(id);
}
}
}
Laut diesem anderen Artikel wäre das Entsorgen von DbContext jedoch nicht unbedingt erforderlich: http://blog.jongallant.com/2012/10/do-i-have-to-call-dispose-on-dbcontext.html
Welcher der beiden Artikel ist richtig? Ich bin ziemlich verwirrt. DbContext als privates Mitglied in Ihrer Repository-Klasse zu haben, kann wirklich "Skalierbarkeitsprobleme" verursachen, wie der erste Artikel vorschlägt?
quelle
Antworten:
Ich denke, Sie sollten dem ersten Artikel nicht folgen , und ich werde Ihnen sagen, warum.
Nach dem ersten Ansatz verlieren Sie so gut wie alle Funktionen, die Entity Framework über das Programm bereitstellt
DbContext
, einschließlich des Caches der ersten Ebene, der Identitätszuordnung, der Arbeitseinheit sowie der Funktionen zur Änderungsverfolgung und zum verzögerten Laden. Dies liegt daran, dass im obigen SzenarioDbContext
für jede Datenbankabfrage eine neue Instanz erstellt und unmittelbar danach entsorgt wird, wodurch verhindert wird, dass dieDbContext
Instanz den Status Ihrer Datenobjekte über den gesamten Geschäftsvorgang hinweg verfolgen kann.Ein
DbContext
als Privateigenschaft in Ihrer Repository-Klasse zu haben, hat auch seine Probleme. Ich glaube, der bessere Ansatz ist ein CustomDbContextScope. Dieser Ansatz wird von diesem Typen sehr gut erklärt: Mehdi El GueddariDieser Artikel http://mehdi.me/ambient-dbcontext-in-ef6/ ist einer der besten Artikel über EntityFramework, die ich gesehen habe. Sie sollten es vollständig lesen, und ich glaube, es wird alle Ihre Fragen beantworten.
quelle
Angenommen, Sie haben mehr als ein Repository und müssen zwei Datensätze aus verschiedenen Repositorys aktualisieren. Und Sie müssen es transaktional tun (wenn einer fehlschlägt - beide Updates rollen zurück):
var repositoryA = GetRepository<ClassA>(); var repositoryB = GetRepository<ClassB>(); repository.Update(entityA); repository.Update(entityB);
Wenn Sie also für jedes Repository einen eigenen DbContext haben (Fall 2), müssen Sie TransactionScope verwenden, um dies zu erreichen.
Besser - haben Sie einen gemeinsamen DbContext für eine Operation (für einen Anruf, für eine Arbeitseinheit ). So kann DbContext Transaktionen verwalten. EF ist genau dafür. Sie können nur einen DbContext erstellen, alle Änderungen in vielen Repositorys vornehmen, SaveChanges einmal aufrufen und nach Abschluss aller Vorgänge und Arbeiten entsorgen.
Hier ist ein Beispiel für die Implementierung eines UnitOfWork-Musters.
Ihr zweiter Weg kann für schreibgeschützte Operationen gut sein.
quelle
Die Grundregel lautet: Ihre DbContext-Lebensdauer sollte auf die Transaktion beschränkt sein, die Sie ausführen .
Hier kann sich "Transaktion" auf eine schreibgeschützte Abfrage oder eine Schreibabfrage beziehen. Und wie Sie vielleicht bereits wissen, sollte eine Transaktion so kurz wie möglich sein.
Trotzdem würde ich sagen, dass Sie in den meisten Fällen die "Verwendung" bevorzugen und kein privates Mitglied verwenden sollten.
Der einzige Fall, in dem ich sehe, dass ein privates Mitglied verwendet wird, betrifft ein CQRS-Muster (CQRS: Eine Kreuzprüfung der Funktionsweise) .
Übrigens gibt die Antwort von Diego Vega im Beitrag von Jon Gallant auch einige weise Ratschläge:
HTH
quelle
Welcher Ansatz verwendet werden soll, hängt von der Verantwortung des Repositorys ab.
Ist das Repository dafür verantwortlich, vollständige Transaktionen auszuführen? dh um Änderungen vorzunehmen und dann Änderungen an der Datenbank zu speichern, indem Sie `SaveChanges? Oder ist es nur ein Teil einer größeren Transaktion und nimmt daher nur Änderungen vor, ohne sie zu speichern?
Fall 1) Das Repository führt vollständige Transaktionen aus (es nimmt Änderungen vor und speichert sie):
In diesem Fall ist der zweite Ansatz besser (der Ansatz Ihres zweiten Codebeispiels).
Ich werde diesen Ansatz nur geringfügig ändern, indem ich eine Fabrik wie diese einführe:
public interface IFactory<T> { T Create(); } public class Repository : IRepository { private IFactory<MyContext> m_Factory; public Repository(IFactory<MyContext> factory) { m_Factory = factory; } public void AddCustomer(Customer customer) { using (var context = m_Factory.Create()) { context.Customers.Add(customer); context.SaveChanges(); } } }
Ich mache diese geringfügige Änderung, um die Abhängigkeitsinjektion zu aktivieren . Dies ermöglicht es uns, später die Art und Weise zu ändern, wie wir den Kontext erstellen.
Ich möchte nicht, dass das Repository die Verantwortung dafür trägt, den Kontext selbst zu erstellen. Die Fabrik, die implementiert
IFactory<MyContext>
, hat die Verantwortung, den Kontext zu erstellen.Beachten Sie, wie das Repository die Lebensdauer des Kontexts verwaltet, den Kontext erstellt, einige Änderungen vornimmt, die Änderungen speichert und dann den Kontext entsorgt. Das Repository hat in diesem Fall eine längere Lebensdauer als der Kontext.
Fall 2) Das Repository ist Teil einer größeren Transaktion (es nimmt einige Änderungen vor, andere Repositorys nehmen andere Änderungen vor und dann wird jemand anderes die Transaktion durch Aufrufen festschreiben
SaveChanges
):In diesem Fall ist der erste Ansatz (den Sie zuerst in Ihrer Frage beschreiben) besser.
Stellen Sie sich vor, Sie werden verstehen, wie das Repository Teil einer größeren Transaktion sein kann:
using(MyContext context = new MyContext ()) { repository1 = new Repository1(context); repository1.DoSomething(); //Modify something without saving changes repository2 = new Repository2(context); repository2.DoSomething(); //Modify something without saving changes context.SaveChanges(); }
Bitte beachten Sie, dass für jede Transaktion eine neue Instanz des Repositorys verwendet wird. Dies bedeutet, dass die Lebensdauer des Repositorys sehr kurz ist.
Bitte beachten Sie, dass ich das Repository in meinem Code neu einstelle (was eine Verletzung der Abhängigkeitsinjektion darstellt). Ich zeige dies nur als Beispiel. In echtem Code können wir Fabriken verwenden, um dies zu lösen.
Eine Verbesserung, die wir bei diesem Ansatz vornehmen können, besteht darin, den Kontext hinter einer Schnittstelle auszublenden, sodass das Repository keinen Zugriff mehr hat
SaveChanges
(siehe das Prinzip der Schnittstellentrennung ).Sie können so etwas haben:
public interface IDatabaseContext { IDbSet<Customer> Customers { get; } } public class MyContext : DbContext, IDatabaseContext { public IDbSet<Customer> Customers { get; set; } } public class Repository : IRepository { private IDatabaseContext m_Context; public Repository(IDatabaseContext context) { m_Context = context; } public void AddCustomer(Customer customer) { m_Context.Customers.Add(customer); } }
Sie können der Schnittstelle weitere Methoden hinzufügen, die Sie benötigen, wenn Sie möchten.
Bitte beachten Sie auch, dass diese Schnittstelle nicht von erbt
IDisposable
. Dies bedeutet, dass dieRepository
Klasse nicht für die Verwaltung der Lebensdauer des Kontexts verantwortlich ist. Der Kontext hat in diesem Fall eine längere Lebensdauer als das Repository. Jemand anderes wird die Lebensdauer des Kontexts verwalten.Anmerkungen zum ersten Artikel:
Der erste Artikel schlägt vor, dass Sie nicht den ersten Ansatz verwenden sollten, den Sie in Ihrer Frage beschreiben (fügen Sie den Kontext in das Repository ein).
Der Artikel ist nicht klar, wie das Repository verwendet wird. Wird es als Teil einer einzelnen Transaktion verwendet? Oder umfasst es mehrere Transaktionen?
Ich vermute (ich bin mir nicht sicher), dass in dem Ansatz, den der Artikel beschreibt (negativ), das Repository als langjähriger Dienst verwendet wird, der viele Transaktionen umfasst. In diesem Fall stimme ich dem Artikel zu.
Was ich hier vorschlage, ist anders. Ich schlage vor, dass dieser Ansatz nur in dem Fall verwendet wird, in dem jedes Mal, wenn eine Transaktion benötigt wird, eine neue Instanz des Repositorys erstellt wird.
Anmerkungen zum zweiten Artikel:
Ich denke, dass das, worüber der zweite Artikel spricht, für den Ansatz, den Sie verwenden sollten, irrelevant ist.
Im zweiten Artikel wird diskutiert, ob es auf jeden Fall erforderlich ist, den Kontext zu entsorgen (irrelevant für das Design des Repositorys).
Bitte beachten Sie, dass wir in den beiden Entwurfsansätzen über den Kontext verfügen. Der einzige Unterschied besteht darin, wer für eine solche Entsorgung verantwortlich ist.
Der Artikel sagt, dass die
DbContext
Ressourcen zu bereinigen scheinen, ohne dass der Kontext explizit verfügbar gemacht werden muss.quelle
IDatabaseContext
innerhalb des Repositorys, wenn es keineDbContext
Methoden enthält , zum Beispiel alscontext.Database.ExecuteSqlCommand
?Der erste Artikel, den Sie verlinkt haben, hat genau eine wichtige Sache vergessen: die Lebensdauer der sogenannten
NonScalableUserRepostory
Instanzen (es wurde auch vergessen, auchNonScalableUserRepostory
ImplementierungenIDisposable
vorzunehmen, um dieDbContext
Instanz ordnungsgemäß zu entsorgen ).Stellen Sie sich folgenden Fall vor:
public string SomeMethod() { using (var myRepository = new NonScalableUserRepostory(someConfigInstance)) { return myRepository.GetMyString(); } }
Nun ... es würde noch einige privat sein
DbContext
Feld innerhalb derNonScalableUserRepostory
Klasse, aber der Kontext wird nur verwendet werden , wenn . Es ist also genau das Gleiche wie das, was der Artikel als Best Practice beschreibt.Die Frage lautet also nicht " Soll ich ein privates Mitglied gegen eine using-Anweisung verwenden? ", Sondern " Was sollte die Lebensdauer meines Kontexts sein? ".
Die Antwort wäre dann: Versuchen Sie, es so weit wie möglich zu verkürzen. Es gibt den Begriff der Arbeitseinheit , der einen Geschäftsbetrieb darstellt. Grundsätzlich sollten Sie
DbContext
für jede Arbeitseinheit eine neue haben .Wie eine Arbeitseinheit definiert und wie sie implementiert wird, hängt von der Art Ihrer Anwendung ab. Beispielsweise ist für eine ASP.Net MVC-Anwendung die Lebensdauer von im
DbContext
Allgemeinen die Lebensdauer vonHttpRequest
, dh jedes Mal, wenn der Benutzer eine neue Webanforderung generiert, wird ein neuer Kontext erstellt.EDIT:
Um Ihren Kommentar zu beantworten:
Eine Lösung wäre, über den Konstruktor eine Fabrikmethode zu injizieren. Hier ist ein grundlegendes Beispiel:
public class MyService : IService { private readonly IRepositoryFactory repositoryFactory; public MyService(IRepositoryFactory repositoryFactory) { this.repositoryFactory = repositoryFactory; } public void BusinessMethod() { using (var repo = this.repositoryFactory.Create()) { // ... } } } public interface IRepositoryFactory { IRepository Create(); } public interface IRepository : IDisposable { //methods }
quelle
using
Anweisung verwendetIRepository
habe, wegwerfbar sein muss. Vielen Dank für die BearbeitungDer erste Code hat keine Beziehung zum Scability-Problem. Der Grund dafür ist, dass er für jedes fehlerhafte Repository einen neuen Kontext erstellt, den einer der Kommentatoren kommentiert, aber nicht einmal geantwortet hat. Im Web war es 1 Anfrage 1 dbContext. Wenn Sie ein Repository-Muster verwenden möchten, wird dies in 1 Anfrage> viele Repositorys> 1 dbContext übersetzt. Dies ist mit IoC leicht zu erreichen, aber nicht erforderlich. So geht's ohne IoC:
var dbContext = new DBContext(); var repository = new UserRepository(dbContext); var repository2 = new ProductRepository(dbContext); // do something with repo
Was das Entsorgen betrifft oder nicht, entsorge ich es normalerweise, aber wenn das Blei selbst dies sagte, dann wahrscheinlich kein Grund, es zu tun. Ich mag es einfach zu entsorgen, wenn es IDisposable hat.
quelle
Grundsätzlich ist die DbContext-Klasse nichts anderes als ein Wrapper, der alle datenbankbezogenen Dinge wie Folgendes behandelt: 1. Verbindung erstellen 2. Abfrage ausführen. Wenn wir nun die oben genannten Dinge mit normalem ado.net ausführen, müssen wir die Verbindung explizit ordnungsgemäß schließen, indem wir entweder den Code in using-Anweisung schreiben oder die Methode close () für das Verbindungsklassenobjekt aufrufen.
Da die Kontextklasse die IDisposable-Schnittstelle intern implementiert, empfiehlt es sich, den dbcontext in using-Anweisung zu schreiben, damit wir uns nicht darum kümmern müssen, die Verbindung zu schließen.
Vielen Dank.
quelle
using
Muster empfehlen).DbContext
kümmert sich auch um Caching, Änderungsverfolgung und vieles mehr. Ich bin mir nicht ganz sicher, aber ich denke, die Verbindung wird bei jedem Datenbankanruf geöffnet und geschlossen.Ich benutze den ersten Weg (Injizieren des dbContext) Natürlich sollte es ein IMyDbContext sein und Ihre Abhängigkeitsinjektions-Engine verwaltet den Lebenszyklus des Kontexts, sodass er nur live ist, während er benötigt wird.
Auf diese Weise können Sie den Kontext zum Testen verspotten. Der zweite Weg macht es unmöglich, die Entitäten ohne Datenbank für den zu verwendenden Kontext zu untersuchen.
quelle
Der zweite Ansatz (Verwenden) ist besser, da er die Verbindung sicher nur für die minimal benötigte Zeit hält und einfacher threadsicher zu machen ist.
quelle
Ich denke, dass der erste Ansatz besser ist, Sie müssen nie einen Datenbankkontext für jedes Repository erstellen, selbst wenn Sie ihn entsorgen. Im ersten Fall können Sie jedoch eine databaseFactory verwenden, um nur einen Datenbankkontext zu instanziieren:
public class DatabaseFactory : Disposable, IDatabaseFactory { private XXDbContext dataContext; public ISefeViewerDbContext Get() { return dataContext ?? (dataContext = new XXDbContext()); } protected override void DisposeCore() { if (dataContext != null) { dataContext.Dispose(); } } }
Und verwenden Sie diese Instanz im Repository:
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class { private IXXDbContext dataContext; private readonly DbSet<TEntity> dbset; public Repository(IDatabaseFactory databaseFactory) { if (databaseFactory == null) { throw new ArgumentNullException("databaseFactory", "argument is null"); } DatabaseFactory = databaseFactory; dbset = DataContext.Set<TEntity>(); } public ISefeViewerDbContext DataContext { get { return (dataContext ?? (dataContext = DatabaseFactory.Get())); } public virtual TEntity GetById(Guid id){ return dbset.Find(id); } ....
quelle