2014-04-06 8 views
6

Moja aplikacja używa 3 warstw: DAL/Service/UL.Auto Dispose Sql Connections prawidłowo

Moja typowa klasa DAL wygląda następująco:

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

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

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

Moje usługi zwraca klasę DAL tak (wstrzykiwanie połączenia z bazą danych):

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

I wreszcie w moim warstwie UI, to jest sposób uzyskiwania dostępu do mojej warstwy usługi:

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

To wszystko działa dobrze. Ale jak widzisz, polegam na IDisposable w każdej warstwie. UI udostępnia obiekt usługi, a następnie obiekt usługi udostępnia obiekt DAL, a następnie obiekt DAL udostępnia obiekt połączenia z bazą danych.

Jestem pewien, że musi być lepszy sposób na zrobienie tego. Obawiam się, że użytkownicy mogą zapomnieć o usunięciu mojego obiektu usługi (w ramach interfejsu użytkownika), a ja będę miał wiele otwartych połączeń z bazami danych lub gorzej. Proszę doradzić najlepszą praktykę. Potrzebuję sposobu automatycznego udostępniania moich połączeń z bazami danych lub innych niezarządzanych zasobów (plików itp.).

Odpowiedz

10

Twoje pytanie wraca do zasady własności:

kto ma prawo własności do zasobu, należy go wyrzucać.

Mimo że prawo własności można przenieść, zwykle nie należy tego robić. W twoim przypadku własność IDbConnection została przeniesiona z ordersService do OrdersRepository (od OrdersRepository zrzuca połączenie). Jednak w wielu przypadkach OrdersRepository nie wie, czy połączenie może zostać usunięte. Może być ponownie wykorzystany na całym wykresie obiektu. Zasadniczo nie należy pozbywać się obiektów przekazywanych za pośrednictwem konstruktora.

Inną kwestią jest to, że konsument zależności często nie może wiedzieć, czy zależność wymaga utylizacji, ponieważ to, czy zależność musi zostać usunięta, jest szczegółem wdrożenia. Te informacje mogą być niedostępne w interfejsie.

Więc zamiast byłaby swój OrdersRepository na następujące kwestie:

public class OrdersRepository : IOrdersRepository { 
    private IDbConnection _db; 

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

Od OrdersRepository nie przejąć na własność, IDbConnection nie trzeba wyrzucać IDbConnection i nie trzeba wdrożyć IDisposable.To wyraźnie przenosi odpowiedzialność za usunięcie połączenia z OrdersService. Jednak sama nazwa ordersService nie wymaga IDbConnection jako zależności; to zależy od IOrdersRepository. Dlaczego więc nie przenieść odpowiedzialność budowania wykresu obiektu z OrdersService, a także:

public class OrdersService : IDisposable { 
    private readonly IOrdersRepository _orders; 

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

Od ordersService ma nic do dysponowania się, że nie ma potrzeby, aby wdrożyć IDisposable. A ponieważ ma teraz tylko jeden konstruktor, który wymaga zależności, klasa staje się łatwiejsza do utrzymania.

Przenosi to odpowiedzialność za tworzenie wykresu obiektu na OrdersController. Ale powinniśmy zastosować ten sam wzór do OrdersController, a także:

public class OrdersController : Controller { 
    private ordersService _orderService; 

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

Ponownie, ta klasa jest znacznie łatwiejsze do uchwycenia i nie potrzebuje do niczego wyrzucać, ponieważ nie ma lub przyjął własność dowolny zasób.

Oczywiście przenieśliśmy się i odłożyliśmy nasze problemy, ponieważ oczywiście nadal musimy stworzyć naszą OrdersController. Ale różnica polega na tym, że przenieśliśmy teraz odpowiedzialność za tworzenie wykresów obiektów do pojedynczego miejsca w aplikacji. Nazywamy to miejsce Composition Root.

Zależność ramy wtryskowe może pomóc podejmowaniu głównej Skład utrzymaniu, ale nawet bez ramy DI, zbudować swój obiekt wykresu dość proste w MVC poprzez wdrożenie niestandardowych 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; 
    } 
} 

można podłączyć swój zwyczaj fabryka kontroler w Global asax swojej aplikacji MVC tak:

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

oczywiście, to wszystko staje się dużo łatwiejsze, kiedy używasz ramy Dependency Injection. Na przykład, podczas korzystania z prostego Injector (jestem główną dev dla prostych Injector), można zastąpić wszystko z następujących kilku liniach kodu:

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

Istnieje kilka ciekawych rzeczy dzieje się w powyższy kod. Przede wszystkim wywołania Register mówią Simple Injector, że muszą zwrócić pewną implementację powinny zostać utworzone, gdy żądana jest abstrakcja. Następnie Simple Injector umożliwia rejestrowanie typów za pomocą Web Request Lifestyle, co zapewnia, że ​​dane wystąpienie zostanie usunięte po zakończeniu żądania WWW (tak jak w przypadku Application_EndRequest). Dzwoniąc pod numer RegisterMvcControllers, Simple Injector sprawdzi wszystkie kontrolery za Ciebie. Dostarczając MVC z SimpleInjectorDependencyResolver, zezwalamy MVC na kierowanie tworzenia kontrolerów do Simple Injector (tak jak robiliśmy to z fabryką sterowników).

Chociaż ten kod może być nieco trudniejszy do zrozumienia na początku, użycie kontenera Dependency Injection staje się bardzo cenny, gdy aplikacja zaczyna się rozwijać. Pojemnik DI pomoże ci utrzymać porządek w strukturze składu.

0

Cóż, jeśli jesteś naprawdę paranoikiem, możesz użyć finalizatora (destruktora), aby automatycznie wykonać utylizację, gdy obiekt jest czyszczony. Sprawdź ten link, który wyjaśnia w zasadzie wszystko, co musisz wiedzieć o IDisposable, przejdź do sekcji Przykłady, jeśli chcesz tylko krótkiej próbki, jak to zrobić. Finalizer (destruktor) to metoda ~ MyResource().

Ale w żaden sposób powinieneś zawsze zachęcać konsumentów twoich bibliotek do właściwego używania Dispose. Automatyczne czyszczenie za pomocą śmieciarza nadal przedstawia wady. Nie wiesz, kiedy śmieciarz wykona swoją robotę, więc jeśli masz dużo tych klas, które są instancjonowane i używane, a potem zapomniane, w krótkim czasie możesz nadal mieć kłopoty.

+0

Już zaglądałem do Finalizera i zauważyłem, że czasami nie wystrzeliło w moim obiekcie warstwy. Szukam bardziej niezawodnego rozwiązania, może za pomocą Ioc lub DI. ale nigdy wcześniej ich nie użyłem i nie wiem od czego zacząć. – highwingers

+0

Jeśli to nie jest odpalanie, to twój obiekt nie jest czyszczony przez kolekcję śmieci i możesz mieć porę pamięci. Jeśli z założenia niektóre przypadki są utrzymywane na dłużej, być może powinieneś przemyśleć, jak ich używasz. –

+1

Należy zaimplementować finalizator tylko w klasie, jeśli ta klasa musi bezpośrednio obsługiwać macierzyste zasoby. W tym przypadku implementacja finalizatora jest bezużyteczna, ponieważ samo 'DbConnection' ma już finalizator. – Steven