17

Jul

[EF] Materializzare una query in modo lazy, senza rinunciare all’IQueryable<T>

Ciao a tutti

 

programmando sistemi di accesso ai dati, e relative logiche di business, spesso si combattono battaglie religiose tra l’uso o no di idiomi

mi schiero tra quelli che sfruttano gli idiomi entro i corretti design per raggiungere al meglio i nostri scopi (Qualità, Produttività, Manutenibilità, Performances, SoC, etc)

 

In questo caso parlando di accesso ai dati abbiamo 2 alternative principali:

 

cablare un transaction-script (un modulo gigantesto) di funzioni basata sugli use-case, ognuna dei quali contenente connessioni e comandi specifici con dentro vari Using per ottimizzare il rilascio delle risorse

 

oppure usare un objec-model decente per la gestione dell’accesso ai dati (vedi table/row-data-gateway) o della relativa logica di business che più che ben si sposa con l’EDM di Entity Framework come l’Active Record

 

ma in questo caso, come facciamo ad oggetti a gestire connessioni, wrapper, e l’IQueryable, idioma di .NET che ci consente di aggiungere ad un expression-tree di predicati, ogni nostro volere come ad esempio Where, OrderBy, GroupBy e proiezioni, tutte poi automaticamente tradotte dal provider di accesso ai dati direttamente sul DB (cosa ke ad 1 boost di performance icomparabile)??

 

Forse una soluzione è la seguente:

 

Ho mappato una semplice tabellina ID, Value col nome di ValuesTable in un EDM di EF

 

image

 

facciamo un semplice esempio: la Top 10, che se eseguita in memoria costringe il sistema a scaricarsi la tabella intera (1000 righe) in ram, poi eseguire il filtro tramite LINQtoObject, ed infine dare i risultati; altrimenti se eseguita sul DB, tradurrà aggiungerà all’sql una top 10:

 

DEMO:

static void Main(string[] args)
{
    //ienumerable
    foreach (var x in ValuesTable.GetValues().Take(10)) //qui la top 10 non viene tradotta sul DB ma eseguita sul client
        Console.WriteLine(x.Value);
    Console.WriteLine("OK");

    //iqueryable
    //questo schianta
    //foreach (var x in ValuesTable.GetValuesAsQueryable().Take(10))
    //    Console.WriteLine(x.Value);

    Console.WriteLine("OK");

    //versione furba
    //qui la top 10 viene eseguita sull'iqueryable, e quindi tradotta su SQL correttamente
    //mantenendo inalterato l'uso lazy dell'apertura del contesto dati di EF
    foreach (var x in ValuesTable.GetValuesWithPredicate(x => x.Take(10)))
        Console.WriteLine(x.Value);

    Console.WriteLine("OK");
    Console.ReadLine();
}

 

PROFILE DI EF SU SQL SERVER (come si traduce la query ad oggetti in SQL):

metodo stupido:

SELECT
[Extent1].[ID] AS [ID],
[Extent1].[Value] AS [Value]
FROM [dbo].[ValuesTable] AS [Extent1]

 

metodo furbo:

SELECT TOP (10)
[c].[ID] AS [ID],
[c].[Value] AS [Value]
FROM [dbo].[ValuesTable] AS [c]

 

C#

//active record per ValuesTable
//http://en.wikipedia.org/wiki/Active_record_pattern
partial class ValuesTable
{
    //versione stupida
    public static IEnumerable GetValues()
    {
        using (var cn = new TestDBEntities())
            return cn.ValuesTable
                .ToArray(); //materializzo la query prima del dispose del contesto e relativa connessione
    }

    //questo metodo non funziona
    //perchè ogni eventuale uso sarebbe fatto su un contesto già distrutto (il dispose automatico dello using)
    //e togliere lo using porterebbe a migliaia di oggetti che gravano sul sistema di GC (garbage collection)
    //che .net dovrà pian piano distruggere per noi
    //ma che contenendo anche connessioni sul DB, andranno a rendere inutile il connection-pooling (di default 100 connessioni)
    //e saturare il numero di connessioni disponibili sul db (di default 600 su sql server)
    public static IQueryable GetValuesAsQueryable()
    {
        using (var cn = new TestDBEntities())
            return cn.ValuesTable;
    }

    //versione furba
    public static IEnumerable GetValuesWithPredicate(Func, IQueryable> predicate = null)
    {
        using (var cn = new TestDBEntities())
            return (predicate == null ? cn.ValuesTable : predicate(cn.ValuesTable)).ToArray();
    }
}

 

spero di avervi dato buone idee Sorriso

ovviamente se estese con l’uso di un Layer specifico, magari con degli T4, il tutto sarà molto molto più comodo!

 

a presto

by Antonio Esposito on 7/17/2012