Domanda

Sto lavorando a un'applicazione che consente ai dentisti di acquisire informazioni su determinate attività cliniche. Sebbene l'applicazione non sia altamente personalizzabile (senza flussi di lavoro o moduli personalizzati), offre alcune funzionalità di personalizzazione rudimentali; i clienti possono scegliere di aumentare i campi modulo predefiniti con i propri campi personalizzati. Esistono circa una mezza dozzina di tipi di campi diversi che gli amministratori possono creare (ad es. Testo, Data, Numerico, DropDown, ecc.). Stiamo usando Entity-Attribute-Value (EAV) sul lato della persistenza per modellare questa funzionalità.

Una delle altre funzionalità chiave dell'applicazione è la possibilità di creare query personalizzate su questi campi personalizzati. Ciò viene realizzato tramite un'interfaccia utente in cui è possibile creare un numero qualsiasi di regole (Data & Lt; = (Ora - 5 giorni), Testo come '444', DropDown == 'ICU'). Tutte le regole sono combinate insieme per produrre una query.

L'implementazione corrente (che ho " ereditato ") non è né orientata agli oggetti né testabile dall'unità. In sostanza, esiste un solo & Quot; God & Quot; classe che compila tutti i miriadi di tipi di regole direttamente in un'istruzione SQL dinamica complessa (vale a dire join interni, join esterni e sottoselezioni). Questo approccio è problematico per diversi motivi:

  • Unità che collauda le singole regole in modo isolato è quasi impossibile
  • Quest'ultimo punto significa anche aggiungere ulteriori tipi di regole in il futuro sicuramente violerà il principio aperto aperto.
  • Le preoccupazioni relative alla logica aziendale e alla persistenza vengono mescolate.
  • Test di unità a esecuzione lenta poiché è necessario un vero database (SQLLite non può analizzare T-SQL e deridere un parser sarebbe uhh ... difficile)

Sto cercando di elaborare un design sostitutivo che sia flessibile, gestibile e testabile, mantenendo comunque le prestazioni della query abbastanza veloci. Quest'ultimo punto è fondamentale poiché immagino che un'implementazione basata su OOAD sposterà almeno una parte della logica di filtraggio dei dati dal server di database al server delle applicazioni (.NET).

Sto prendendo in considerazione una combinazione dei modelli di comando e catena di responsabilità:

La classe Query contiene una raccolta di classi Rule astratte (DateRule, TextRule, ecc.). e contiene un riferimento a una classe DataSet che contiene un set di dati non filtrato. DataSet è modellato in modo agnostico di persistenza (ovvero senza riferimenti o hook nei tipi di database)

La regola ha un singolo metodo Filter () che accetta un DataSet, lo filtra in modo appropriato e quindi lo restituisce al chiamante. La classe Query che semplicemente scorre su ogni regola, consentendo a ciascuna regola di filtrare il DataSet come ritiene opportuno. L'esecuzione si interrompe dopo che tutte le regole sono state eseguite o dopo che il DataSet è stato filtrato fino a zero.

L'unica cosa che mi preoccupa di questo approccio sono le implicazioni sulle prestazioni dell'analisi di un set di dati non filtrati potenzialmente di grandi dimensioni in .NET. Sicuramente ci sono alcuni approcci provati e veri per risolvere proprio questo tipo di problema che offrono un buon equilibrio tra manutenibilità e prestazioni?

Un'ultima nota: la direzione non consentirà l'uso di NHibernate. Linq to SQL potrebbe essere possibile, ma non sono sicuro di quanto tale tecnologia sarebbe applicabile all'attività da svolgere.

Mille grazie e attendo il feedback di tutti!

Aggiornamento: sto ancora cercando una soluzione su questo.

È stato utile?

Soluzione

Penso che LINQ to SQL sarebbe una soluzione ideale accoppiata, forse, con Dynamic LINQ dagli esempi VS2008. Usando LINQ, in particolare con i metodi di estensione su IEnumerable / IQueryable, puoi creare le tue query usando la tua logica standard e personalizzata a seconda degli input che ottieni. Uso fortemente questa tecnica per implementare filtri su molte delle mie azioni MVC con grande efficacia. Dal momento che in realtà costruisce un albero delle espressioni e quindi lo utilizza per generare l'SQL nel punto in cui la query deve essere materializzata, penso che sarebbe l'ideale per il tuo scenario poiché la maggior parte del sollevamento pesante viene ancora eseguito dal server SQL. Nei casi in cui LINQ provi a generare query non ottimali, puoi sempre utilizzare le funzioni con valori di tabella o le procedure memorizzate aggiunte al tuo contesto di dati LINQ come metodi per sfruttare le query ottimizzate.

Aggiornato : potresti anche provare a utilizzare PredicateBuilder da C # 3.0 in breve.

Esempio: trova tutti i libri in cui il titolo contiene uno di una serie di termini di ricerca e l'editore è O'Reilly.

 var predicate = PredicateBuilder.True<Book>();
 predicate = predicate.And( b => b.Publisher == "O'Reilly" );
 var titlePredicate = PredicateBuilder.False<Book>();
 foreach (var term in searchTerms)
 {
     titlePredicate = titlePredicate.Or( b => b.Title.Contains( term ) );
 }
 predicate = predicate.And( titlePredicate );

 var books = dc.Book.Where( predicate );

Altri suggerimenti

Il modo in cui l'ho visto è creare oggetti che modellano ciascuna delle condizioni da cui l'utente desidera creare la propria query e costruire un albero di oggetti usando quelle.

Dall'albero degli oggetti dovresti essere in grado di creare ricorsivamente un'istruzione SQL che soddisfi la query.

Quelli di base di cui avrai bisogno saranno gli oggetti AND e OR, così come gli oggetti per modellare il confronto, come EQUALS, LESSTHAN ecc. Probabilmente vorrai usare un'interfaccia per questi oggetti per farli concatenare insieme in diversi molto più semplice.

Un esempio banale:

public interface IQueryItem
{
    public String GenerateSQL();
}


public class AndQueryItem : IQueryItem
{
    private IQueryItem _FirstItem;
    private IQueryItem _SecondItem;

    // Properties and the like

    public String GenerateSQL()
    {
        StringBuilder builder = new StringBuilder();
        builder.Append(_FirstItem.GenerateSQL());
        builder.Append(" AND ");
        builder.Append(_SecondItem.GenerateSQL());

        return builder.ToString();
    }
}

L'implementazione in questo modo dovrebbe permetterti di testare le regole abbastanza facilmente.

Sul lato negativo, questa soluzione lascia ancora il database per fare molto del lavoro, il che sembra che tu non voglia davvero fare.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top