2009-11-09 11 views
11

Desidero creare un repository DDD che restituisca le entità IQueryable che corrispondono alle classi sottostanti di Linq a SQL, meno le relazioni. Posso facilmente restituire Entità meno le relazioni con una Linq selezionare una nuova {field, field, ...} proiezione. Come si codifica la classe Entity repository? Come faccio a restituire un oggetto dal repository avere la classe repository non il LINQ to SQL di classe ed ancora riempirlo con più entità dalla selezione Linq? Come faccio a fare riferimento a questa classe restituita nel mio ViewModel?ASP.MVC: Repository che riflette IQueryable ma non Linq a SQL, DDD How To question

Sono molto nuovo a questo, quindi gli errori evidenti. Mi manca la barca e dovrei restituire solo Entità complete dal repository, non una proiezione? Avrei comunque bisogno di rimuovere le relazioni da Linq a SQL prima di inviarle dal repository. Sono totalmente fuori base? Voglio davvero mantenere il tipo di dati IQueryable.

Per esempio, il mio LINQ to SQL di codice nel mio repository:

public class MiniProduct 
{ 
    public MiniProduct(string ProductNo, string Name, string Description, double Price) 
    { this.ProductNo = ProductNo; 
     this.Name = Name; 
     this.Description = Description; 
     this.Price = Price; 
    } 
} 

public IQueryable<MiniProduct> GetProductsByCategory(string productCategory) 
{ 
    return (from p in db.Products 
      from c in db.Categories 
      from pc in db.ProductCategories 
      where c.CategoryName == productCategory && 
        c.CatID == pc.CatID && 
        pc.ProductID == p.ProductID 
      select new { p.ProductNo, p.Name, p.Description, p.Price }); 
    // how to return IQueryable<MiniProduct> instead of IQueryable<AnonymousType>??? 
} 

E alla vista (cercando di tipo fortemente ViewModel) quello che sarebbe stato il mio modello di tipo di dati e come fare riferimento dal punto di vista?

<% Page Inherits="System.Web.Mvc.ViewPage<MyStore.Models.MiniProduct>" %> 

Edit:

Cottsak il potere del codice e ha reso il lavoro, così lui guadagna la casella di controllo. Tuttavia, Mark Seemann ha sottolineato che questa tecnica causerà effetti collaterali. Aveva ragione in quel progetto o sotto-impostazione che il tuo POCO è cattivo. Dopo aver fatto il codice di lavoro, ho finito per fare una tonnellata più una tantum oggetti entità, che provoca inutili complicazioni. Alla fine ho cambiato il codice per riflettere i suggerimenti di Mark.

Per aggiungere ai suggerimenti di Cottsak: Il mio valore di ritorno repository era IQueryable. Il tipo di modello di riferimento direttiva pagina è stata

Inherits="System.Web.Mvc.ViewPage<IQueryable<MyStore.Models.MiniProduct>>" 

I campi modello erano accessibili da:

Model.SingleOrDefault().ProductNo 
Model.SingleOrDefault().Name 
... 

E questo ha portato ad una

foreach (MyStore.Models.MiniProduct myproduct in Model) {} 

Vi ringrazio entrambi per le risposte.

+0

L'idea di visualizzazione fortemente corretta è corretta. Il tuo esempio è giusto. –

risposta

8

provare qualcosa di simile:

public class AnnouncementCategory : //... 
{ 
    public int ID { get; set; } 
    public string Name { get; set; } 
} 

.. e in pronti contro termine:

public IQueryable<AnnouncementCategory> GetAnnouncementCategories() 
{ 
    return from ac in this._dc.AnnouncementCategories 
      let announcements = this.GetAnnouncementsByCategory(ac.ID) 
      select new AnnouncementCategory 
      { 
       ID = ac.ID, 
       Name = ac.Text, 
       Announcements = new LazyList<Announcement>(announcements) 
      }; 
} 

private IQueryable<Announcement> GetAnnouncementsByCategory(int categoryID) 
{ 
    return GetAnnouncements().Where(a => a.Category.ID == categoryID); 
} 

questo modo, invece di proiettare in un tipo anonimo, sto proiettando in una nuova istanza di la mia classe AnnouncementCategory. Puoi ignorare la funzione GetAnnouncementsByCategory se lo desideri, questa opzione viene utilizzata per concatenare la raccolta di oggetti associati Announcement per ogni categoria, in modo che possano essere caricati in modo pigro con IQueryable (ad esempio, quando i fare questa proprietà, non devo chiamare l'intera collezione, posso fare più LINQ su questo per filtrare).

+0

Fatemi sapere se avete bisogno di maggiori informazioni sul concatenamento dei grafici degli oggetti con classi di caricamento lazy. È divertente –

+0

Wow. Stamperò questo e lo metto sul mio muro di tutti i codici più belli che abbia mai visto. Usando l'istruzione let definisci un set quindi il carico pigro impostato in seguito nella stessa query è davvero notevole. Dove trovi questi schemi? –

+0

Wow .. non pensavo fosse così fantastico. Ma mi ci è voluto un bel po 'di ricerche per trovare la parte "let" - prima che usassi let (e la funzione separata - che tra l'altro è - assolutamente necessaria per questo lavoro 2) tutto funzionerebbe ma eccetto l'interrogazione pigra (differita esecuzione). Senza l'uso di let e la funzione tutti gli oggetti concatenati nel grafico interrogano immediatamente (come un trigger di enumerazione con LinqToSql). Questo metodo è documentato dalla SM e per quanto ne so è l'unico modo per mappare l'esecuzione differita su un altro modello di init pigro. Classi pigri che uso: http://bit.ly/1Aaxx5 –

29

Supponendo che le vostre classi LINQ to SQL (L2S) sono generati automaticamente e riflette il vostro database sottostante, la risposta breve è: non esporre IQueryable di qualsiasi delle classi L2S - sarebbe un Leaky Astrazione.

La risposta leggermente più lunga:

Il punto di repository è quella di nascondere l'accesso ai dati dietro un'astrazione in modo da poter sostituire o variare il vostro codice di accesso ai dati indipendentemente dal vostro Domain Model. Ciò non sarà possibile quando baserete l'interfaccia del repository su tipi definiti all'interno di un'implementazione specifica (il vostro DAC (Data Access Component) basato su L2S) - anche se poteste fornire una nuova implementazione dell'interfaccia del repository, avreste bisogno di fai riferimento al tuo DAC L2S. Che non sarebbe giocare particolarmente bello se improvvisamente deciso di passare a LINQ to Entities, oppure il servizio di archiviazione Azure Table.

dominio oggetti devono essere definiti in modo tecnologicamente neutrale. Questo è meglio fatto come Plain Old C# Objects (POCO).

Inoltre, l'esposizione di IQueryable consente di eseguire proiezioni. Può sembrare attraente, ma in realtà è piuttosto pericoloso in un contesto DDD.

In DDD, dovremmo progettare oggetti dominio in modo che incapsulino la logica di dominio e garantiscano tutti gli invarianti.

Ad esempio, considerare il concetto di entità (non un LINQ per entità entità, ma una entità DDD). Le entità sono quasi sempre identificate da un ID persistente, quindi un invariante comune è che l'ID deve essere definito. Potremmo scrivere una classe base Entità simili:

public abstract class Entity 
{ 
    private readonly int id; 

    protected Entity(int id) 
    { 
     if(id <= 0) 
     { 
      throw new ArgumentOutOfRangeException(); 
     } 
     this.id = id; 
    } 

    public int Id 
    { 
     get { return this.id; } 
    } 
} 

Tale classe ben tutela i propri invarianti (in questo caso che l'ID deve essere un numero positivo). Potrebbero esserci molti altri invarianti specifici del dominio implementati in particolari classi, ma molti di questi sono molto probabilmente in disaccordo con il concetto di proiezione: potresti essere in grado di definire una proiezione che omette determinate proprietà (come l'ID), ma si bloccherà in fase di runtime perché i valori predefiniti per i tipi (come 0 per int) genereranno eccezioni.

In altre parole, anche se è necessario solo determinate proprietà di un oggetto dominio, sarà solo sensato idratarlo nella sua interezza, poiché questo è l'unico modo per soddisfare i suoi invarianti.

In ogni caso, se si rileva spesso che è necessario selezionare solo alcune parti dei propri oggetti di dominio, ciò potrebbe essere dovuto al fatto che violano il principio di responsabilità singola. Potrebbe essere un'idea migliore quella di svuotare la classe del dominio in due o più classi.

In conclusione, esporre IQueryable suona come una strategia molto allettante, ma con l'attuale implementazione di L2S porterà a Leaky Abstractions e Anemic Domain Models.

Con la prossima versione di Entity Framework, dovremmo ottenere il supporto POCO, in modo che ci possa avvicinare a tale obiettivo. Tuttavia, per quanto mi riguarda, la giuria è ancora fuori per quel conto.


Per una discussione più approfondita di un argomento molto simile, vedi IQueryable is Tight Coupling.

+0

Un grande miglioramento per il mio apprendimento è stato il tuo commento sulle classi di repository mai sub-setting e con l'eliminazione di Linq su IQueryable di Sql. Tuttavia, la classe POCO che creiamo non agisce come un cookie cutter che rimuove il Linq originale da Sql IQueryable e le relazioni, creando un nuovo oggetto IQueryable?Non penserei che questo sia diverso dall'elenco . Ciò consente anche di restituire cose come LazyList (oggetto IQueryable ) o pensi che questo dovrebbe essere un livello in cima al repository, come AOP o preoccupazioni trasversali? –

+0

@Zim: Non penso che sia quello che Mark sta dicendo. Non sono così chiaro con DDD come Mark, ma credo che i principi fondamentali di DDD sembrano essere violati secondo l'opinione di Mark: vale a dire, incapsulare correttamente il 'Dominio' all'interno del Modello di Dominio (dati e comportamento [Fowler]). Uno dei più facili. Posso vedere come abilitare la proiezione di L2S su oggetti POCO (Domain?) Potrebbe rendere difficile progettare oggetti di dominio in modo DDD, tuttavia sarei felice di tentare di giustificare la mia implementazione di un Repo restituito 'IQueryable' sotto i vincoli DDD per scopi pratici ... –

+0

... aggiungo il caveat "scopi pratici" perché so che i puristi del DDD potrebbero non essere d'accordo con certi aspetti del mio design ma credo che includa -enough- DDD per beneficiare del DDD ed essere pratico con il tecnologie disponibili. Con riferimento specifico a LinqToSql, vorrei confessare che 'IQueryable' corre il rischio di accoppiare saldamente il modello POCO alle differenze LinqToSql con LINQ puro, ma sono anche sicuro che le mie classi 'Lazy *' rimuovano appropriatamente questo accoppiamento in modo tale che il mio modello vola sotto il suo stesso vapore. –