2009-12-28 11 views
7

Mam prostą klasę, która ma być zwykłym POCO - po prostu przechowuje dane. Z jednym wyjątkiem: zawiera kolekcję notatek. Chcę wczytać tę kolekcję w luźny sposób, aby nie musieć pobierać Notatek na Stronach, które ich nie potrzebują. Kod pośredniczący to:Leniwe ładowanie kolekcji - jak zdobyć przedmioty?

public class MyDTOClass 
{ 
    private ICollection<Note> _notes = null; 

    public ICollection<Note> Notes 
    { 
     get 
     { 
      if(_notes == null) 
      { 
       // Get an INoteRepository and initialize the collection 
      } 
      return _notes; 
     } 
    } 
} 

Teraz zastanawiam się, jak przejść dalej. Jest to aplikacja ASP.net MVC i używam Dependency Injection do wstrzykiwania IRepositentów w klasy, które ich potrzebują, na przykład moje kontrolery. Ale ponieważ ta klasa tutaj ma być naprawdę prostym DTO, niechętnie wstrzyknę do niej INOTRepository, także dlatego, że osoba dzwoniąca nie powinna się martwić ani nie przejmować faktem, że jest ona załadowana leniwo.

Tak więc myślę o innej klasie w moim modelu, który posiada INoteRepository.

public class MyDataAccessClass 
{ 
    private INoteRepository _noteRepo; 

    // Inject is part of Ninject and makes sure I pass the correct 
    // INoteRepository automatically 
    [Inject] 
    public MyDataAccessClass(INoteRepository noteRepository) 
    { 
     _noteRepo = noteRepository; 
    } 

    public IEnumerable<Note> GetNotes(int projectId) 
    { 
     return _noteRepo.GetNotes(projectId); 
    } 
} 

To zadziała oczywiście, ale zastanawiam się, czy to jest poprawna architektura? Łączę prosty DTOClass z inną klasą Data Access i być może również z moim mechanizmem DI (ponieważ potrzebuję stworzyć instancję klasy Data Access w programie pobierającym Notatki).

Czy zrobiłbyś to inaczej? Czy jest lepszy sposób na zrobienie tego, pamiętając o tym, że już korzystam z Ninject?

Zgaduję, że nie jest to już POCO lub DTO, ponieważ zawiera teraz logikę, ale to jest w porządku. Chcę, aby pojawił się jak POCO dla rozmówcy zewnętrznego, więc lubię mieć właściwość "Uwagi", a nie metody takie jak "GetNotesForProject" na tej lub innych klasach.

Moje obecne rozwiązanie jest naprawdę brzydkie, ponieważ potrzebuję uzyskać jądro Ninject z mojej aplikacji MvcApplication i użyć go do zakodowania klasy ProjectDataProvider, która pobiera InoteRepository w swoim konstruktorze, aby uniknąć konieczności umieszczania INECO-Receptor gdzieś w moim " DTO "-class:

public ICollection<Note> Notes 
{ 
    get 
    { 
     if(_notes == null) 
     { 
      var app = HttpContext.Current.ApplicationInstance as MvcApplication; 
      if (app == null) 
      throw new InvalidOperationException("Application couldn't be found"); 
      var pdp = app.Kernel.Get<ProjectDataProvider>(); 
      _notes = new List<Note>(pdp.GetNotes(Id)); 
     } 
     return _notes; 
    } 
} 

Edit: Otwarty bounty. Zignorujmy terminologię "POCO" i "DTO", będę odpowiednio odnawiać. A więc chodzi o to: w jaki sposób powinien wyglądać kod Lazy-Loading w takiej sytuacji i czy powinienem/powinnam unikać przekazywania do komórki MyDTOClass elementu INoteRepository?

Odpowiedz

8

Twoje DTO nie musi wiedzieć o samym repozytorium. Wszystko, czego potrzebuje, to delegat, który może dostarczyć mu wartość banknotów.

Jak o coś takiego:

public class MyDTOClass 
{ 
    private ICollection<Note> _notes = null; 

    public ICollection<Note> Notes 
    { 
     get 
     { 
      if (_notes == null) 
      { 
       if (notesValueProvider == null) 
        throw new InvalidOperationException("ValueProvider for notes is invalid"); 
       _notes = notesValueProvider(); 
      } 
      return _notes; 
     } 
    } 

    private Func<ICollection<Note>> notesValueProvider = null; 

    public MyDTOClass(Func<ICollection<Note>> valueProvider) 
    { 
     notesValueProvider = valueProvider; 
    } 
} 

Ponieważ z definicji repozytorium ma dostarczyć instancji DTO, powinniśmy być w stanie przejść w provider delegata wartości tak:

public class Repository 
{ 
    public MyDTOClass GetData() 
    { 
     MyDTOClass dto = new MyDTOClass(FetchNotes); 
     return dto; 
    } 

    public ICollection<Note> FetchNotes() 
    { 
     return new List<Note>(200); 
    } 
} 

Czy to zadziała?

+1

To jest najlepsza odpowiedź IMO, dostarcza rozwiązania i nieodłącznie uzależnia swoją klasę DTO od IRepository, jestem pewien, że mam widziałem ten wzór zaimplementowany w sposób wielokrotnego użytku albo jako ILazy lub AbstractLazy . –

+1

Ya .. Myślę, że to dokładnie to robi Lazy w .net 4. – madaboutcode

+0

Tak, to jest metoda, której również bym użył. Programowanie funkcyjne 4tw. =) –

2

Pokonujesz cały cel DTO, próbując dodać do niego logikę ładującą. Myślę, że powinieneś mieć dwa oddzielne obiekty: jeden z Note s, a drugi - bez nich.

+0

Prawdopodobnie, ale nie jestem pewien, czy chcę go złamać. Czy poleciłbyś posiadanie MyDTOClass bez Notatek, a następnie jego podklasę jako MyDTOClassWithNotes: MyDTOClass, która dodaje notatki (zignoruj ​​złe Nazewnictwo)? Wciąż mógłbym przekazać klasę WithNotes do funkcji, które oczekują tylko MyDTOClass. Po prostu nie wiem, czy jest coś przeciw temu, oprócz oczywiście dodatkowej klasy. –

+0

Zgadzam się z Antonem. Twój DTO powinien odzwierciedlać dokładnie to, czego potrzebuje ten widok. Jeśli potrzebuje notatek, DTO powinien zawierać notatki. Jeśli widok wymaga wczytania nut, to dodaj widok z DTO bez notatek i zapewnij mechanizm dla widoku (widok częściowy?), Aby wykonać leniwy ładunek własnych notatek dla danej klasy za pomocą kontrolera Zaprojektowany, aby to zrobić. Dzięki temu klasy Domain, DTO, kontrolery i widoki są czyste i proste. –

0

Możesz mieć właściwość Notes przyjmować INoteRepository jako parametr. W ten sposób kod wywołujący może przejść w prawidłowym wystąpieniu INoteRepository.

+0

To sprawiłoby, że byłaby to jednak metoda i chciałbym, aby konsument MyDTOClass nie wiedział/dbał o to - powinien być przejrzysty. W przeciwnym razie prawdopodobnie całkowicie wyprowadzę go z klasy. –

+0

Moja wina, pracuję ostatnio w VB.NET, który pozwala przekazywać parametry do właściwości i zapomniał, że C# nie obsługuje tego. :-( –

1

Po wędrowaniu po królestwie astralnym przez eony desperacko poszukując odpowiedzi, doszedłem do ostatecznego wniosku, że tak, nie trzeba przekazywać repozytorium do instancji jednostki, ponieważ repozytorium dla typu jednostki powinno być zawsze singleton.

Więc można napisać w swojej klasie jednostki po prostu coś takiego:

public class Monster 
{ 
    public ReadOnlyCollection<Victim> _victims; 
    public ReadOnlyCollection<Victim> Victims 
    { 
     get 
     { 
      if (this._victims == null) // Note: Thread-Safety left out for brevity 
      { 
       this._victims = VictimRepository.Instance.GetVictimsForMonster(this); 
      } 

      return this._victims; 
     } 
    } 
} 

To naprawdę rozwiązać wszystkie bóle na mnie.

Repozytorium musi być zaimplementowane w taki sposób, aby zawsze wiedział, co zrobić z danymi.

Pamiętaj, że jedna implementacja repozytorium może na przykład uzyskać dane z bazy danych, podczas gdy inna może pobrać je z usługi WWW. Ze względu na luźne połączenie, moduł implementacji repozytorium można łatwo wymieniać, a każda transmisja danych jest nawet dowolnie kierowana.

Jeśli masz scenariusz, w którym powiesz "ale repozytorium nie może być pojedyncze, ponieważ mam złożony scenariusz, w którym mam na przykład dostęp do więcej niż jednego źródła danych i zależy to od rzeczywistej instancji Monster gdzie można uzyskać Victim s od ", wtedy mówię, że musisz utworzyć implementację repozytorium, która zna wszystkie źródła danych i śledzi, skąd pochodzą instancje jednostek i dokąd się udadzą, i tak dalej ...

Jeśli czujesz to podejście nie jest wystarczającym POCO, musisz utworzyć kolejną luźno powiązaną warstwę Business Logic, która owija się lub wywodzi z jednostki POCO i implementuje tam interakcję repozytorium.

Mam nadzieję, że mogę podać wskazówkę w kierunku, który będzie odpowiedni dla Ciebie i każdego. Naprawdę wierzę, że jest to święty Graal dla rozwoju wielowarstwowego /. Zapraszam do dalszej dyskusji.

+0

Myślałem o czymś takim, problem polega tylko na tym, że nie znam nazwy klasy, która implementuje repozytorium, ponieważ jest tworzona przez Dependency Injection, ale przypuszczam, że potrzebuję statycznej klasy Factory. .. –

+0

Yap to z pewnością więcej sposobów na zrobienie tego, ja często bawiłem się przy pomocy IMonsterRepository i fabryki oraz innych rzeczy, ale mimo wszystko, jeśli to singleton, potrzebuję abstrakcyjnej klasy bazowej dla repozytorium. wywoła Resolver, który wie, jak utworzyć instancję dla repozytorium, a następnie ją buforować – herzmeister

2

Inną opcją jest użycie serwerów proxy, które dziedziczą z obiektów, które chcesz odzyskać (podążając za przykładem niektórych odwzorowań obiektowo-relacyjnych, takich jak NHibernate).

ten zapewnia pewien stopień trwałości niewiedzy poprzez utrzymywanie kodu dostępu do danych oddzielone od modelu domeny:

public class MyLazyDTOClass: MyDTOClass { 

    // Injected into the constructor by the MyDtoClass repository 
    private INoteRepository noteRepository;   

    public ICollection<Note> Notes { 
     get { 
      if(base.Notes == null) { 
       base.Notes = noteRepository.GetNotes(projectId); 
      } 
      return base.Notes; 
     } 
    } 
} 

MyDTOClassRepository deklaruje obiekt bazowy jako typ zwracany ale zwraca leniwe obiekt zamiast:

public MyDTOClassRepository { 
    public MyDTOClass GetMyDTOClass(int id) { 
     // ... Execute data access code to obtain state ... 
     return new MyLazyDTOClass(this, state); 
    } 
} 

MyDTOClass Konsumenci nie muszą wiedzieć, że mają do czynienia z serwerami proxy, ani też nie muszą wchodzić w interakcje z repozytoriami (z wyjątkiem sytuacji, w której każda klasa będzie początkowo dzwonić).

+0

Interesujący pomysł. Nawet początkowy rozmówca mógłby zostać odłączony, przekazując wiedzę o INoteRepository klasie MyDTOClassRepository (Maybe Factory jest lepszym terminem). Trzeba z tym trochę eksperymentować. –

1

Nie możesz uzyskać niezależności od swojego repozytorium, jeśli jesteś leniwy ładując z niego. Możesz zachować go na wyciągnięcie ręki, używając wywołania zwrotnego lub serwera proxy, lub umożliwiając NHibernate wykonanie brudnej roboty, ale twój DTO musi uzyskać dostęp do repozytorium, aby załadować Notatki.

Twoim głównym celem wydaje się być "Chcę, aby wyglądało to jak POCO dla rozmówcy zewnętrznego, więc lubię mieć właściwość" Uwagi ", a nie metody takie jak" GetNotesForProject "na tej lub innych klasach." Nie możesz tego dokonać za pomocą iniekcji ninject i constructor? Po ustawieniu ninject możesz zadzwonić do jądra.Get(), aby uzyskać nowe wystąpienie, które nie narazi żadnego odniesienia do twojego repozytorium.

+0

Tak, właśnie to robię teraz, ale jak widać powyżej, nie ma naprawdę wygodnego sposobu dostępu do jądra, ponieważ znajduje się on w aplikacji MvcApplication. Otworzyłem pytanie, aby zobaczyć, jakie jest najlepsze podejście: Przenieś jądro do klasy statycznej, aby była dostępna globalnie, lub w jakikolwiek sposób rozłącz aplikacje, aby moje "DTO" nie wymagało przekazania jądra lub repozytorium w. –

Powiązane problemy