Che cosa è un modo ideale per passare regole / opzioni ai metodi che creano SQL

0

Stiamo cercando di trovare un modo per gestire il codice che crea SQL dinamico per la nostra applicazione, che è molto centrato sul database. Cose come Linq to SQL e Entity Framework sono fuori questione, quindi per favore non ci sono suggerimenti di questo tipo, il nostro percorso è stato definito per noi.

Il nostro obiettivo principale è la separazione di SQL dal resto del codice (business logic, ecc.).

In questo momento, stiamo pensando di utilizzare i metodi di estensione per ospitare l'SQL e di creare una sorta di oggetto di configurazione che gli sviluppatori possono utilizzare per definire come deve essere costruita la query (dettata dalla logica aziendale, al di fuori dell'estensione classe)

Quindi, ad esempio, ho un codice prototipo sotto. Gli sviluppatori installerebbero la Stringa di configurazione (come da regole aziendali) e la passeranno a GetSQL.

public static class LocationSql2Extension
{

    public enum SelectFields
    {
        Default,
        DefaultAnd_Description_LocationSize
    }

    public enum FilterBy
    {
        Default,
        DefaultAnd_Description
    }

    public enum OrderBy
    {
        Default,            
        DefaultAnd_LocationType_LocationID_Description
    }

    public struct Configuration
    {
        public SelectFields SelectFields;
        public FilterBy FilterBy;
        public OrderBy OrderBy;
    }


    public static string GetSQL(this LocationService2 service, Configuration config)
    {

        string sqlSelectFields = string.Empty;
        string sqlWhere = string.Empty;
        string sqlOrderBy = string.Empty;

        if (config.SelectFields == SelectFields.DefaultAnd_Description_LocationSize)
        {
            sqlSelectFields = ", Description, LocationSize";
        }

        if (config.FilterBy == FilterBy.DefaultAnd_Description)
        {
            sqlWhere = "WHERE L.Description = ?";
        }

        if (config.OrderBy == OrderBy.DefaultAnd_LocationType_LocationID_Description)
        {             
            sqlOrderBy = ", L.LocationDescription";
        }

        string sql = string.Format(@"
                                       SELECT L.LocationType, L.LocationId
                                              {0}
                                       FROM Location AS L
                                       INNER JOIN GroupLoc AS GL
                                       ON  L.LocationType = GL.LocationType 
                                           AND L.LocationId = GL.LocationId 
                                           AND GL.SecLocLevel >= 4 
                                           AND GL.GroupId = ?
                                       {1}
                                       ORDER BY L.LocationType||L.LocationId
                                       {2}
                                      ", sqlSelectFields, sqlWhere, sqlOrderBy);

        return sql;

    }       
}

e per chiamare il codice precedente, abbiamo questo:

public class LocationService2
{
    public string BuildSql()
    {
        var config = new LocationSql2Extension.Configuration();

        //TODO: Add Business Logic to drive these values
        config.SelectFields = LocationSql2Extension.SelectFields.Default;
        config.OrderBy = LocationSql2Extension.OrderBy.DefaultAnd_LocationType_LocationID_Description;
        config.FilterBy = LocationSql2Extension.FilterBy.DefaultAnd_Description;

        return this.GetSQL(config);            

    }
}

Gli sviluppatori sarebbero liberi di configurare l'Enum come preferiscono, e questo è solo un semplice esempio. Non ci sentiamo a nostro agio con questo approccio Enum / Struct e stiamo cercando qualcosa di più generico per il quale gli sviluppatori possano lavorare facilmente, ma non è così generico da non rilevare errori in fase di progettazione. Questo è un semplice esempio e nel nostro sistema abbiamo alcune query molto grandi e complesse. Qualche suggerimento?

    
posta Brett Emerson 05.03.2014 - 00:14
fonte

2 risposte

1

Quello che stai dicendo è che non puoi utilizzare un ORM ben supportato, quindi stai cercando di inventare il tuo ORM limitato per compensare. Non sta andando bene. Dal punto di vista delle prestazioni, i moderni ORM si distinguono molto bene e sono abbastanza fluidi da consentire di eseguire la maggior parte delle operazioni che è possibile eseguire con un server di database in SQL raw.

Detto questo, se vuoi seguire questo tipo di percorso, potresti avere più successo lasciando SQL essere SQL ed esporre un'API logica alla tua base di codice. Ciò che significa da un punto di vista pratico è non costruire un oggetto come il tuo esempio che espone metodi di dati generici, ma piuttosto esporre i creatori di query che sono un po 'più significativi. Internamente è possibile creare alcune classi di helper e classi base per gestire alcuni dei lavori più complessi della generazione SQL, ma in generale se si sta per costruire l'SQL si potrebbe anche sfruttare la fluidità di SQL e costruire SQL. Questo non è particolarmente scalabile ma se il dominio è ben definito puoi farcela.

Per quel che vale, spesso adottiamo questo tipo di approccio alla definizione della query e lo avvolgiamo attorno a un ORM, così il nostro codice client è relativamente inconsapevole delle meccaniche del database dietro le quinte.

    
risposta data 05.05.2014 - 04:01
fonte
0

Ti vedo usare le enumerazioni, tuttavia sembrerebbe che tu stia passando collezioni per cose come campi / colonne di database.

Per prima cosa dovrai definire una collezione che costituisce la vista - [nome del database]. [schema]. [nome tabella]. [nome colonna], che viene condensato come appropriato in un alias a livello nome colonna. La tua classe dovrebbe verificare che i nomi alias siano unici. Poiché questa vista ha varie regole per i join, avrai una collezione di espressioni di join. La tua classe dovrà verificare che questi non siano ridondanti e validi per gli oggetti di database reali.

Una volta definita la tua vista, avrai una collezione di espressioni "where", che possono essere annidate. Pertanto ogni dato elemento nella collezione potrebbe avere la propria collezione. Dovrai navigare verso i nodi terminali e comporre l'SQL in bit, quindi assemblarli con i parenti appropriati mentre torni indietro verso la radice.

Il tuo "ordine per" è una raccolta con un ordine di classifica forzata, che elenca gli alias in base a tale ordine.

Questo è semplificato, almeno nel senso che non hai selezioni incorporate e non stai usando Group By o Having. In quelle situazioni dovresti rendere l'intera struttura ricorsiva.

    
risposta data 05.03.2014 - 10:35
fonte

Leggi altre domande sui tag