2009-12-28 13 views
7

Ho una semplice classe che è destinata ad essere un semplice POCO: contiene solo dati. Con una sola eccezione: contiene una raccolta di note. Voglio caricare pigro questa raccolta in modo che non debba recuperare le note su pagine che non ne hanno bisogno. Lo stub per questo è questo:Caricamento pigro della raccolta: come ottenere gli articoli?

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

Ora, mi chiedo come procedere da qui. È un'applicazione ASP.net MVC e uso Dependency Injection per iniettare gli IRepositories nelle classi che ne hanno bisogno, ad esempio i miei controller. Ma siccome questa classe qui dovrebbe essere un DTO veramente semplice, sono riluttante a iniettarvi un INoteRepository, anche perché il chiamante non dovrebbe preoccuparsi o preoccuparsi del fatto che questo è pigro-caricato.

Quindi sto pensando di avere un'altra classe nel mio modello che contiene un 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); 
    } 
} 

Questo funzionerebbe ovviamente, ma mi chiedo se questa è l'architettura corretta? Accoppiato il DTOClass semplice a un'altra classe di accesso ai dati e probabilmente anche al mio meccanismo DI (poiché ho bisogno di creare un'istanza della classe Data Access nel getter di Notes).

Lo faresti diversamente? C'è un modo migliore per farlo, anche tenendo presente che utilizzo già Ninject?

Suppongo che questo non sia più un POCO o un DTO poiché ora contiene la logica, ma va bene. Voglio che a compaia come come un POCO per il chiamante esterno, quindi mi piace avere una proprietà "Note" piuttosto che metodi come "GetNotesForProject" su questa o altre classi.

La mia soluzione attuale è davvero brutta, in quanto ho bisogno di ottenere il kernel Ninject da MvcApplication e usarlo per far ruotare la classe ProjectDataProvider che prende un INoteRepository nel suo costruttore, per evitare di dover mettere il file INoteRepository da qualche parte nel mio " 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: aperto una taglia. Ignoriamo la terminologia di "POCO" e "DTO", mi rifatterò di conseguenza. Quindi si tratta di: Come dovrebbe apparire il codice Lazy-Loading in una situazione del genere e posso/dovrei evitare di passare INoteRepository nel MyDTOClass?

risposta

8

Il DTO non ha bisogno di conoscere il repository stesso. Tutto ciò di cui ha bisogno è un delegato in grado di fornirgli il valore per le note.

Che ne dite di qualcosa di simile:

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

poiché per definizione, il repository dovrebbe fornire con un'istanza del DTO, dovremmo essere in grado di passare nel provider delegato valore in questo modo:

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

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

Questo lavoro farà al caso vostro?

+1

Questa è la migliore risposta IMO, fornisce la soluzione e non rende intrinsecamente dipendente la sua classe DTO su un IRepository, sono sicuro di averlo visto questo modello implementato in modo riutilizzabile sia come ILazy o AbstractLazy . –

+1

Ya .. Penso che questo io s esattamente ciò che Lazy in. Net 4 fare. – madaboutcode

+0

Sì, questo è il metodo che userei pure. Programmazione funzionale 4tw. =) –

2

Si sta vanificando l'intero scopo delle DTO quando si tenta di aggiungere la logica di caricamento lento ad esso. Penso che dovresti avere due oggetti separati: uno con Note s e un altro - senza di essi.

+0

Forse, ma non sono sicuro di volerlo fratturare. Consiglieresti di avere MyDTOClass senza le Note e quindi la sottoclassi come MyDTOClassWithNotes: MyDTOClass che aggiunge le note (ignora il nome errato)? Potrei ancora passare nella classe WithNotes in funzioni che si aspettano solo MyDTOClass. Semplicemente non so se c'è qualcosa contro, a parte ovviamente per avere una lezione aggiuntiva. –

+0

Sono d'accordo con Anton. Il tuo DTO dovrebbe riflettere esattamente ciò di cui ha bisogno la vista. Se ha bisogno delle note, il DTO dovrebbe contenere le note. Se la vista ha bisogno delle note caricate pigro, quindi fornire la vista con un DTO senza le note e fornire un meccanismo per la vista (una vista parziale?) Per eseguire il caricamento lazy delle proprie note per una determinata classe utilizzando un controller progettato per fare proprio questo. Ciò mantiene le classi di dominio, i DTO, i controller e le visualizzazioni pure e semplici. –

0

È possibile che la proprietà di Notes includa un INoteRepository come parametro. In questo modo il codice chiamante potrebbe passare nell'istanza corretta di INoteRepository.

+0

Tuttavia, sarebbe un metodo, e mi piacerebbe che il consumatore di MyDTOClass non lo sapesse/si preoccupasse di questo - dovrebbe essere trasparente. Altrimenti potrei spostarlo completamente fuori dalla classe. –

+0

Per colpa mia, ho lavorato ultimamente in VB.NET che consente di passare i parametri in proprietà e ho dimenticato che C# non supporta questo. :-( –

1

Dopo aver vagato per reami nei regni astrali alla disperata ricerca di una risposta, sono arrivato alla conclusione finale che sì, non è necessario passare un repository nell'istanza dell'entità, perché un repository per un tipo di entità dovrebbe essere sempre un singleton.

Così si potrebbe scrivere nella tua classe di entità semplicemente qualcosa di simile:

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

Questo in realtà risolto tutti i mal di testa per me.

Il repository deve essere implementato in modo che possa sempre sapere cosa fare con i dati.

Ricordare che un'implementazione di repository, ad esempio, otterrebbe i dati dal database, mentre un'altra potrebbe ottenerla da un servizio Web. A causa dell'accoppiamento lento, il modulo di implementazione del repository è facilmente sostituibile e qualsiasi comunicazione di dati è persino arbitrariamente concatenabile.

Se si dispone di uno scenario in cui si potrebbe dire "ma il repository non può essere singleton perché ho uno scenario complesso in cui, ad esempio, accedo a più di un'origine dati e dipende dall'istanza effettiva Monster dove ottenere il Victim da ", quindi dico bene è necessario creare un'implementazione repository che conosce tutte le origini dati e tiene traccia di dove provengono le istanze delle entità e dove andranno, e così via ...

Se si sente questo l'approccio non è sufficientemente POCO, quindi è necessario creare un altro livello di business logic liberamente accoppiato che si avvolga o derivi dall'entità POCO e implementa l'interazione del repository in quel punto.

Spero di poter dare un suggerimento in una direzione che sembra giusta per te e per chiunque. In realtà credo che questo sia il Santo Graal per lo sviluppo multistrato/più avanzato. Sentiti libero di discutere ulteriormente.

+0

Stavo pensando a qualcosa del genere, il problema è solo che non conosco il nome della classe che implementa il repository dato che è stato creato attraverso Dependency Injection, ma suppongo di aver bisogno di una classe Factory statica. .. –

+0

Yap ci sono sicuramente più modi per farlo: ero solito giocare con IMonsterRepository e factory e anche roba, ma dopo tutto, se è singleton, ho solo bisogno di una classe base astratta per il repository. chiamerebbe un Resolver che sa come creare un'istanza per un repository e poi lo memorizza nella cache – herzmeister

2

Un'altra opzione consiste nell'utilizzare i proxy che ereditano dagli oggetti che si desidera recuperare (seguendo la guida di alcuni mapper relazionali oggetto, come NHibernate).

Ciò fornisce un grado di persistenza dell'ignoranza, mantenendo il codice di accesso ai dati indipendente dal modello di dominio:

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 dichiara l'oggetto di base come il suo tipo di ritorno, ma restituisce l'oggetto pigro invece:

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

MyDTOClass i consumatori non hanno bisogno di sapere che hanno a che fare con i proxy, né devono interagire con i repository (eccetto per qualsiasi classe faccia ovviamente la chiamata iniziale).

+0

Idea interessante. Anche il chiamante iniziale potrebbe eventualmente disaccoppiare dando la conoscenza di INoteRepository alla classe MyDTOClassRepository (Forse Factory è un termine migliore). Devo sperimentare un po '. –

1

Non è possibile ottenere l'indipendenza dal repository se si sta scaricando da esso. Puoi tenerlo a distanza usando un callback o un proxy o lasciando che NHibernate faccia il lavoro sporco per te, ma il tuo DTO deve accedere al repository per caricare Notes.

Il tuo obiettivo principale sembra essere "Voglio che appaia come un POCO al chiamante esterno, quindi mi piace avere una proprietà" Note "piuttosto che metodi come" GetNotesForProject "su questa o altre classi." Non puoi farlo con l'iniezione di ninject e costruttore? Una volta che hai impostato ninject puoi chiamare il kernel.Ottieni() per ottenere una nuova istanza che non esponga alcun riferimento al tuo repository.

+0

Sì, questo è quello che sto facendo in questo momento, ma come si vede sopra, non c'è un modo veramente conveniente per accedere al Kernel mentre si trova in MvcApplication. Ho aperto la domanda per vedere quale sarebbe l'approccio migliore: Spostare il kernel in una classe statica per renderlo globalmente disponibile, o in qualche modo disaccoppiare l'applicazione abbastanza che il mio "DTO" non ha bisogno del kernel o di un repository passato in. –

Problemi correlati