Mi sa che mi devo preoccupare… ma che gli frega a Visual Studio di quello che sta facendo Outlook?
.m
The journey is the most important thing, not the destination. Find your next destination and start travelling again.
Nulla di trascendentale, se non fosse che non va…, mentre questo invece funziona:String expected = "select [_t0].[id], [_t0].[firstName], [_t0].[lastName] from [Persons] as [_t0]"; DbRepositoryQueryProvider provider = new DbRepositoryQueryProvider(); TestPersonRepository repository = new TestPersonRepository( provider ); var query = from person in repository select person; String actual = provider.GetQueryText( query.Expression );
nonostante all’apparenza sia più complesso, e lo è sia ben chiaro. Ma quali sono le differenza tra il primo ed il secondo?String expected = "select [_t0].[id], [_t0].[firstName], [_t0].[lastName] from [Persons] as [_t0] where [_t0].[firstName] = ‘Mauro’"; DbRepositoryQueryProvider provider = new DbRepositoryQueryProvider(); TestPersonRepository repository = new TestPersonRepository( provider ); var query = repository.Where( p => p.FirstName == "Mauro" ); String actual = provider.GetQueryText( query.Expression );
repository.Select( p => p );e infatti produce lo stesso identico expression tree. Ma sappiamo molto bene che in realtà la chiamata a Select() ci serve solo in 2 casi:
var query = repository.Where( p => p.FirstName == "Mauro" );e tutto funziona come deve senza esplicitare la select, cosa necessaria nella query linq per il solo fatto che è necessaria dal punto di vista del compilatore che deve capire cosa state facendo. Ma torniamo al nostro problema… e mi tocca fare un’altro passo indietro. IQueryProvider (System.Linq) non è sufficiente, nonostante sia il cuore di tutto, perchè non è l’entry point per la generazione di una “query”.
var query = repository.Where( p => p.FirstName == “Mauro” )Ogni chiamata genera un nuovo expression tree, che è immutabile (importantissimo), che sta a voi memorizzare nella vostra implementazione di IQueryable
.OrderBy( p => p.LastName )
.Skip( 10 )
.Take( 5 )
.Select( p => new { DisplayName = String.Format( “{0} {1}”, p.FirstName, p.LastName ) } );
A questo punto viene il bello, dato l’expression tree avete l’onere di analizzarlo e generare innanzitutto il command, gli eventuali parameters, e infine mandarlo in esecuzione recuperare u cursore forward only (IDataReader) e scorrendo i record creare (projection) le istanze delle entity.foreach( var element in query ) { Console.WriteLine( element ); }
repository.Where( p => true );che probabilmente non ha molto senso ma è decisamente lecito e che deve essere trasformato in una cosa del tipo:
select * from [Persons] where 1 = 1che ancora non è detto che abbia senso ma è sicuramente lecito, ma non solo potremmo sbizzarrirci e produrre:
repository.Where( p => myMethod( p ) );e qui sono cavoli amari perchè nell’expression tree avete una MethodCallExpression per myMethod e avete l’arduo compito di capire, ed è tutt’altro che semplice, se sia possibile eseguire quella chiamata in maniera remota, ergo su sql server ad esempio, o se invece debba essere eseguita localmente con conseguente fetch di tutti i dati e filtro in memoria… ma potremmo peggiorare:
repository.Where( p => myMethod( p ) ).OrderBy( p => p.FirstName );mescolando ulteriormente le carte in tavola al provider che deve essere in grado di capire che la prima chiamata deve essere eseguita localmente ma la seconda può essere eseguita su sql server, deve quindi interpretare quell’expression tree come se fosse stato scritto:
repository.OrderBy( p => p.FirstName ).Where( p => myMethod( p ) );che non so se sia lecito, ad esempio per Linq2Sql, ma rende bene l’idea.
Person aPerson = myPersonRepository.GetById( “mauro” );Questo probabilmente produce una chiamata verso un db con un comando sql, i dati tronano indietro viene istanziata una Person e i dati vengono “spinti” nell’istanza di Person appena creata.
IDataContext session = //Bla bla…l’esempio è triviale, in pseudo codice e potrebbe essere adattato a molti O/RM (o simili…) ma ancora una volta non è questo l’argomento, come non lo è il concetto di transazione di business ne tanto meno quello di data context.
Person aPerson = session.Get( “queryDefinition” );
La topologia di rete dei client che usano la nostra applicazione cambia/evolve, rendendo necessario uno switch da multi-layer a multi-tier perchè alcuni client adesso sono remoti, di mezzo c’è un “malefico” firewall e da li ci passa solo https443.
IServiceContainer sc = ServiceContainer.GetContainer();E’ fattibile? certo che si! quello snippet compila e funziona pure… producendo questo sql:
using( IDataContext dc = sc.GetService() )
{
var query = dc.Persons.Where( p => p.FirstName == “Mauro” );
IEntityCollectionlist = query.ToEntityCollection( dc );
//Operate on list…
dc.Commit();
}
select [_t0].[id], [_t0].[firstName], [_t0].[lastName] from [Persons] as [_t0] where [_t0].[firstName] = @p1Cosa è costato? un bagno di sangue :-D oltre al dettaglio che quello funziona mentre questo neanche a morire:
var query = from person in dc.Persons where person.FirstName == “Mauro” select person;…nonostante, per lo sviluppatore che l’ha scritto, siano esattamente la stessa cosa… solo per lui purtroppo :-D, ma anche questa è un’altra storia.