2014-04-06 7 views
6

Meine Anwendung verwendet 3 Schichten: DAL/Service/UL.Auto Sql-Verbindungen ordnungsgemäß entsorgen

Meine typische DAL-Klasse sieht wie folgt aus:

public class OrdersRepository : IOrdersRepository, IDisposable 
{ 
    private IDbConnection _db; 

    public OrdersRepository(IDbConnection db) // constructor 
    { 
     _db = db; 
    } 

    public void Dispose() 
    { 
     _db.Dispose(); 
    } 
} 

Mein Dienst ruft die DAL-Klasse wie folgt aus (eine Datenbankverbindung Injektion):

public class ordersService : IDisposable 
{ 
    IOrdersRepository _orders; 

    public ordersService() : this(new OrdersRepository(new Ajx.Dal.DapperConnection().getConnection())) 
    { 
    } 

    public ordersService(OrdersRepository ordersRepo) 
    { 
     _orders = ordersRepo; 
    } 

    public void Dispose() 
    { 
     _orders.Dispose(); 
    } 
} 

Und dann endlich in meiner UI-Ebene, diese ist, wie ich auf meine Service-Ebene zugreifen:

public class OrdersController : Controller, IDisposable 
{ 
    // 
    // GET: /Orders/ 
    private ordersService _orderService; 

    public OrdersController():this(new ordersService()) 
    { 
    } 

    public OrdersController(ordersService o) 
    { 
     _orderService = o; 
    } 

    void IDisposable.Dispose() 
    { 
     _orderService.Dispose(); 
    } 
} 

Das alles funktioniert gut. Aber wie Sie sehen können, verlasse ich mich innerhalb jeder Schicht auf IDisposable. Das UI verfügt über ein Serviceobjekt und das Serviceobjekt über das DAL-Objekt und das DAL-Objekt über das Datenbankverbindungsobjekt.

Ich bin mir sicher, dass es einen besseren Weg geben muss, es zu tun. Ich fürchte, Benutzer können vergessen, mein Service-Objekt (innerhalb der Benutzeroberfläche) zu entsorgen, und ich werde am Ende mit vielen offenen Datenbankverbindungen oder schlimmer enden. Bitte geben Sie die Best Practice an. Ich brauche eine Möglichkeit, meine Datenbankverbindungen ODER andere nicht verwaltete Ressourcen (Dateien usw.) automatisch zu disponieren.

Antwort

10

Ihre Frage kommt zu dem Grundsatz der Eigenverantwortung zurück:

wer das Eigentum an der Ressource hat, sollte es entsorgen.

Obwohl Eigentumsrechte übertragen werden können, sollten Sie dies normalerweise nicht tun. In Ihrem Fall wird der Besitz des IDbConnection von ordersService auf OrdersRepository übertragen (seit OrdersRepository verfügt über die Verbindung). Aber in vielen Fällen kann der OrdersRepository nicht wissen, ob die Verbindung entsorgt werden kann. Es kann im gesamten Objektdiagramm wiederverwendet werden. Im Allgemeinen sollten Sie Objekte, die über den Konstruktor an Sie übergeben werden, nicht entfernen.

Eine andere Sache ist, dass der Verbraucher einer Abhängigkeit oft nicht wissen kann, ob eine Abhängigkeit entsorgt werden muss, denn ob eine Abhängigkeit entsorgt werden muss, ist ein Implementierungsdetail. Diese Information ist möglicherweise in der Schnittstelle nicht verfügbar.

Also statt, Refactoring Ihre OrdersRepository auf die folgenden:

public class OrdersRepository : IOrdersRepository { 
    private IDbConnection _db; 

    public OrdersRepository(IDbConnection db) { 
     _db = db; 
    } 
} 

Da OrdersRepository nicht Besitz nimmt, IDbConnection braucht nicht IDbConnection zu verfügen und Sie müssen nicht IDisposable implementieren.Dies verschiebt explizit die Verantwortung für die Entsorgung der Verbindung zu OrdersService. Jedoch benötigt ordersService an sich nicht IDbConnection als eine Abhängigkeit; Es kommt nur auf IOrdersRepository. Warum also nicht bewegen, um die Verantwortung der Aufbau des Objektgraphen aus dem OrdersService auch:

public class OrdersService : IDisposable { 
    private readonly IOrdersRepository _orders; 

    public ordersService(IOrdersRepository ordersRepo) { 
     _orders = ordersRepo; 
    } 
} 

Da ordersService nichts selbst zu entsorgen hat, gibt es keine Notwendigkeit, IDisposable zu implementieren. Und da es jetzt nur noch einen Konstruktor gibt, der die erforderlichen Abhängigkeiten übernimmt, ist die Verwaltung der Klasse viel einfacher geworden.

So verschiebt dies die Verantwortung der Erstellung des Objektdiagramms auf die OrdersController. Aber wir sollten das gleiche Muster auf die OrdersController gelten auch:

public class OrdersController : Controller { 
    private ordersService _orderService; 

    public OrdersController(ordersService o) { 
     _orderService = o; 
    } 
} 

Auch dieser Klasse viel leichter zu erfassen und es tut braucht nichts zu entsorgen, da tut es nicht hat oder nahm das Eigentum an jede Ressource.

Natürlich sind wir gerade umgezogen und haben unsere Probleme verschoben, da wir natürlich noch unsere OrdersController erstellen müssen. Der Unterschied besteht jedoch darin, dass wir jetzt die Verantwortung für den Aufbau von Objektdiagrammen an einen einzigen Ort in der Anwendung verlagert haben. Wir nennen diesen Ort Composition Root.

Dependency Injection-Frameworks können Ihnen helfen, Ihre Zusammensetzung Wurzel wartbar zu machen, aber auch ohne DI Rahmen, können Sie Ihre Objektgraph ganz einfach in MVC erstellen, indem Sie eine benutzerdefinierte Implementierung ControllerFactory:

public class CompositionRoot : DefaultControllerFactory { 
    protected override IController GetControllerInstance(
     RequestContext requestContext, Type controllerType) { 
     if (controllerType == typeof(OrdersController)) { 
      var connection = new Ajx.Dal.DapperConnection().getConnection(); 

      return new OrdersController(
       new OrdersService(
        new OrdersRepository(
         Disposable(connection)))); 
     } 
     else if (...) { 
      // other controller here. 
     } 
     else { 
      return base.GetControllerInstance(requestContext, controllerType); 
     } 
    } 

    public static void CleanUpRequest() } 
     var items = (List<IDisposable>)HttpContext.Current.Items["resources"]; 
     if (items != null) items.ForEach(item => item.Dispose()); 
    } 

    private static T Disposable<T>(T instance) 
     where T : IDisposable { 
     var items = (List<IDisposable>)HttpContext.Current.Items["resources"]; 
     if (items == null) { 
      HttpContext.Current.Items["resources"] = 
       items = new List<IDisposable>(); 
     } 
     items.Add(instance); 
     return instance; 
    } 
} 

Sie Ihre benutzerdefinierten Haken können Controller Factory in der globalen asax Ihrer MVC-Anwendung wie folgt aus:

public class MvcApplication : System.Web.HttpApplication 
{ 
    protected void Application_Start() 
    { 
     ControllerBuilder.Current.SetControllerFactory(
      new CompositionRoot()); 
    } 

    protected void Application_EndRequest(object sender, EventArgs e) 
    { 
     CompositionRoot.CleanUpRequest(); 
    } 
} 

natürlich ist dies alles wird viel einfacher, wenn Sie ein Dependency Injection-Framework verwenden. Zum Beispiel, wenn Sie einfachen Injector verwenden (Ich bin die Führung Entwickler für Simple Injector), können Sie all dies mit den folgenden paar Zeilen Code ersetzen:

using SimpleInjector; 
using SimpleInjector.Integration.Web; 
using SimpleInjector.Integration.Web.Mvc; 

public class MvcApplication : System.Web.HttpApplication 
{ 
    protected void Application_Start() 
    { 
     var container = new Container(); 

     container.RegisterPerWebRequest<IDbConnection>(() => 
      new Ajx.Dal.DapperConnection().getConnection()); 

     container.Register<IOrdersRepository, OrdersRepository>(); 
     container.Register<IOrdersService, OrdersService>(); 

     container.RegisterMvcControllers(Assembly.GetExecutingAssembly()); 

     container.Verify(); 

     DependencyResolver.SetResolver(
      new SimpleInjectorDependencyResolver(container)); 
    } 
} 

Es gibt ein paar interessanten Dinge, die sie in der Code oben. Zuerst, die Aufrufe an Register sagen Simple Injector, dass sie eine bestimmte Implementierung zurückgeben müssen, sollte erstellt werden, wenn die angegebene Abstraktion angefordert wird. Als nächstes erlaubt Simple Injector das Registrieren von Typen mit der Web Request Lifestyle, die sicherstellt, dass die gegebene Instanz bei Beendigung der Web-Anfrage entsorgt wird (genau wie wir es in der Application_EndRequest getan haben). Mit dem Aufruf RegisterMvcControllers registriert Simple Injector alle Controller für Sie. Indem wir MVC mit der SimpleInjectorDependencyResolver versorgen, erlauben wir MVC, die Erstellung von Steuerungen an den einfachen Injektor zu leiten (genau wie wir es mit der Fabrik der Steuerung gemacht haben).

Obwohl dieser Code auf den ersten Blick etwas schwieriger zu verstehen ist, wird die Verwendung eines Dependency Injection-Containers sehr nützlich, wenn Ihre Anwendung zu wachsen beginnt. Ein DI-Container wird Ihnen helfen, Ihre Composition Root wartbar zu halten.

0

Nun, wenn Sie wirklich paranoid sind, können Sie den Finalizer (Destruktor) verwenden, um Dispose automatisch auszuführen, wenn das Objekt Garbage Collected ist. Überprüfen Sie diesen Link, der im Wesentlichen alles erklärt, was Sie über IDisposable wissen müssen, gehen Sie zum Beispiele-Abschnitt, wenn Sie nur eine schnelle Probe wie das tun möchten. Der Finalizer (Destruktor) ist die Methode ~ MyResource().

Aber auf jeden Fall sollten Sie immer die Verbraucher Ihrer Bibliotheken ermutigen, das Dispose korrekt zu verwenden. Die automatische Bereinigung mittels Müllsammler weist immer noch Mängel auf. Sie wissen nicht, wann der Garbage Collector seine Arbeit machen wird. Wenn Sie also viele dieser Klassen instanziiert und verwendet haben, dann vergessen Sie, dass Sie in kurzer Zeit immer noch in Schwierigkeiten sind.

+0

Ich schaute bereits in Finalizer, und ich bemerkte, dass es manchmal nicht innerhalb meines Service-Layer-Objekts ausgelöst wurde. Ich suche nach einer robusteren Lösung, vielleicht mit Ioc oder DI. aber ich habe sie vorher nie benutzt und weiß nicht wo ich anfangen soll. – highwingers

+0

Wenn es nicht ausgelöst wird, wird Ihr Objekt nicht von der Speicherbereinigung bereinigt, und Sie haben möglicherweise einen Speicher-Lauch. Wenn durch Design einige Instanzen für eine lange Suration gehalten werden, dann sollten Sie vielleicht darüber nachdenken, wie Sie sie verwenden. –

+1

Sie sollten nur einen Finalizer in einer Klasse implementieren, wenn diese Klasse direkt mit nativen Ressourcen umgehen muss. In diesem Fall ist die Implementierung eines Finalizers nutzlos, da 'DbConnection' selbst bereits einen Finalizer besitzt. – Steven