2009-03-07 10 views
5

Qual è il modo consigliato per restituire dati ad hoc (personalizzati caso per caso) dal repository che non si adattano ad alcuna entità modello o che ne estendono alcuni?Schema di dati e repository ad hoc

L'esempio 101 sarebbe l'onnipresente applicazione della parola ciao: un sistema di blog. Supponiamo di voler caricare un elenco di post in cui la voce post ha alcune informazioni aggiuntive che non esistono nell'entità Post. Diciamo che è il numero di commenti e la data e l'ora dell'ultimo commento. Questo sarebbe molto banale se si stesse usando il semplice vecchio SQL e leggendo i dati direttamente dal database. Come faccio a farlo in modo ottimale usando il modello di repository se non posso permettermi di caricare l'intera collezione di commenti per ogni post, e voglio farlo in un colpo di database? C'è qualche schema comunemente usato per questa situazione? Ora immagina di avere un'applicazione web moderatamente complessa in cui ogni pagina richiede dati personalizzati leggermente diversi e non è possibile caricare gerarchie complete (prestazioni, requisiti di memoria, ecc.).

Alcune idee casuali:

  1. aggiungere un elenco di oggetti da ogni modello che potrebbe essere popolato dai dati personalizzati.

  2. Sottoclasse le entità del modello caso per caso e creare lettori personalizzati per ciascuna sottoclasse.

  3. Utilizzare LINQ, comporre query ad hoc e leggere classi anonime.

Nota: ho chiesto un similar question recently, ma sembrava di essere troppo generico e non attirare molta attenzione.

Esempio:

Sulla base di suggerimenti in risposte di seguito, sto aggiungendo un esempio più concreto. Qui è la situazione che stava cercando di descrivere:

IEnumarable<Post> posts = repository.GetPostsByPage(1); 
foreach (Post post in posts) 
{ 

    // snip: push post title, content, etc. to view 

    // determine the post count and latest comment date 
    int commentCount = post.Comments.Count(); 
    DateTime newestCommentDate = post.Comments.Max(c => c.Date); 

    // snip: push the count and date to view 

} 

Se non faccio nulla in più e utilizzare un largo della piattaforma ORM, questo si tradurrà a n + 1 query o, eventualmente, una query di carico tutti i post e commenti . Ma in modo ottimale, mi piacerebbe essere in grado di eseguire solo uno SQL che restituirebbe una riga per ogni post incluso il titolo del post, il corpo ecc. E il conteggio dei commenti e la data di commento più recente nella stessa. Questo è banale in SQL. Il problema è che il mio repository non sarà in grado di leggere e adattare questo tipo di dati al modello. Dove vanno le date massime e il conteggio?

Non sto chiedendo come farlo. Puoi sempre farlo in qualche modo: aggiungi metodi aggiuntivi al repository, aggiungi nuove classi, entità speciali, usa LINQ ecc., Ma suppongo che la mia domanda sia la seguente. Come mai il modello di repository e il corretto sviluppo guidato dal modello sono così ampiamente accettati, ma non sembrano affrontare questo caso apparentemente molto comune e di base.

risposta

0

non posso dire davvero vedere qual è il problema, solo sparando in aria qui:

  • Aggiungi un'entità specifica per incapsulare informazioni yo vogliono
  • aggiunge una proprietà commenti al post.(Non vedo perché questo richiederebbe il recupero di tutti i commenti - è possibile recuperare i commenti per il particolare post che si sta caricando)
  • Utilizzare il caricamento lazy per recuperare solo i commenti quando si accede alla proprietà

Penso che avresti maggiori possibilità di vedere la tua domanda risposta se vuoi rendere piattaforma, lingua e mapper O/R specifici (sembra essere .NET C# o VB, dato che hai citato LINQ LINQ 2 SQL? Entity framework ? Qualcos'altro?)

+0

Grazie per avermi indicato. Ho aggiunto un semplice esempio concreto e ulteriori spiegazioni. –

1

C'è molto a questa domanda. Hai bisogno di questi dati specifici per una procedura di segnalazione? In tal caso, la soluzione corretta è quella di disporre di un accesso ai dati separato a scopo di segnalazione. Database appiattiti, viste, ecc.

Oppure è necessaria una query ad hoc? Se è così, Ayende ha un post su questo stesso problema. http://ayende.com/Blog/archive/2006/12/07/ComplexSearchingQueryingWithNHibernate.aspx

Utilizza un oggetto "Finder". Sta usando NHibernate, quindi essenzialmente quello che sta facendo è creare una query separata.

Ho fatto qualcosa di simile in passato creando un oggetto Query che posso riempire prima di passarlo a un repository (alcuni puristi del DDD ne discuteranno, ma lo trovo elegante e facile da usare).

L'oggetto Query implementa un'interfaccia fluida, così posso scrivere questo e ottenere i risultati indietro:

IQuery query = new PostQuery() 
    .WithPostId(postId) 
    .And() 
    .WithCommentCount() 
    .And() 
    .WithCommentsHavingDateLessThan(selectedDate); 


Post post = _repository.Find(query); 

Tuttavia, nel vostro caso specifico mi chiedo a vostro disegno. Stai dicendo che non puoi caricare i commenti con il post. Perché? Ti senti troppo preoccupato per le prestazioni? È un caso di ottimizzazione prematura? (mi sembra così)

Se avessi un oggetto Post sarebbe la mia radice aggregata e sarebbe arrivato con i Commenti allegati. E poi tutto ciò che vuoi fare funzionerebbe in ogni scenario.

+0

Grazie. I tuoi suggerimenti sembrano un buon inizio. Mi chiedo dove conti effettivamente il conteggio dei commenti? Sicuramente, non esiste un membro dati separato per questo nell'entità Post. –

+0

Per quanto riguarda le prestazioni, l'esempio con i post del blog era solo un esempio. La vera applicazione che ho in mente è già in esecuzione e non possiamo davvero permetterci di caricare l'intera collezione. –

+0

Il vero dominio del problema fa la differenza. È impossibile per chiunque dire se hai un difetto di progettazione con il tuo dominio, che potrebbe effettivamente portare a una soluzione migliore. Post e commenti è un problema risolutivo e le tue domande non hanno un senso contestuale. –

1

Dato che dovevamo risolvere urgentemente il problema descritto nella mia domanda iniziale, abbiamo fatto ricorso alla seguente soluzione. Abbiamo aggiunto una raccolta di proprietà (un dizionario) a ogni entità del modello e, se il DAL ha bisogno, attacca i dati personalizzati in. Per stabilire un qualche tipo di controllo, la collezione di proprietà è definita da istanze di una classe designata e supporta solo tipi di dati semplici (numeri interi, date, ...) che è tutto ciò di cui abbiamo bisogno in movimento, e per lo più probabilmente avrà sempre bisogno . Un caso tipico che risolve questo è: caricare un'entità con i conteggi per le sue sottoraccolte invece di raccolte popolate complete. Sospetto che questo probabilmente non ottenga alcun premio per un design del software, ma è stata la soluzione più semplice e più pratica per il nostro caso.

+0

Penso che l'altra opzione avrebbe dovuto collegare alcune query denominate alle entità e utilizzare quelle nei repository. Domanda interessante però, peccato che così poche persone sembrassero ingannare. – wds

0

Se non si è bloccati in un RDBM, un database come CouchDB o Amazons SimpleDB potrebbe essere qualcosa da guardare. Quello che stai descrivendo è banale in una vista CouchDB. Questo probabilmente non ti risponde in modo specifico ma a volte è bene guardare a opzioni radicalmente diverse.

0

Per questo in genere ho un RepositoryStatus e una classe Status che funge da DTO (Data Transfer Object). La classe Status viene utilizzata nel livello del servizio applicazioni (per lo stesso motivo) dal quale eredita RepositoryStatus. Quindi con questa classe posso restituire messaggi di errore, oggetti di risposta, ecc. Dal livello Repository. Questa classe è generica in quanto accetta qualsiasi oggetto e lo lancia per il ricevitore.

Qui è la classe di stato:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using RanchBuddy.Core.Domain; 
using StructureMap; 

namespace RanchBuddy.Core.Services.Impl 
{ 
    [Pluggable("Default")] 
    public class Status : IStatus 
    { 
     public Status() 
     { 
      _messages = new List<string>(); 
      _violations = new List<RuleViolation>(); 
     } 

     public enum StatusTypes 
     { 
      Success, 
      Failure 
     } 

     private object _object; 
     public T GetObject<T>() 
     { 
      return (T)_object; 
     } 
     public void SetObject<T>(T Object) 
     { 
      _object = Object; 
     } 

     private List<string> _messages; 
     public void AddMessage(string Message) 
     { 
      _messages.Add(Message); 
     } 
     public List<string> GetMessages() 
     { 
      return _messages; 
     } 
     public void AddMessages(List<string> Messages) 
     { 
      _messages.AddRange(Messages); 
     } 

     private List<RuleViolation> _violations; 
     public void AddRuleViolation(RuleViolation violation) 
     { 
      _violations.Add(violation); 
     } 
     public void AddRuleViolations(List<RuleViolation> violations) 
     { 
      _violations.AddRange(violations); 
     } 
     public List<RuleViolation> GetRuleViolations() 
     { 
      return _violations; 
     } 
     public StatusTypes StatusType { get; set; } 
    } 
} 

E qui è il RepositoryStatus:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using RanchBuddy.Core.Services.Impl; 
using StructureMap; 

namespace RanchBuddy.Core.DataAccess.Impl 
{ 
    [Pluggable("DefaultRepositoryStatus")] 
    public class RepositoryStatus : Status, IRepositoryStatus 
    { 

    } 
} 

Come si può vedere la RepositoryStatus ancora non fa nulla di speciale e appena si basa sugli oggetti di stato utilities. Ma volevo riservare il diritto di estendere in un secondo momento!

Sono sicuro che alcuni dei duri a morire là fuori dichiareranno che questo non dovrebbe essere usato se si vuole essere un prueist ... comunque so che il tuo dolore in questo a volte devi passare più di un semplice un oggetto restituito!

Problemi correlati