Tuesday, October 12, 2010

Combinare due query LINQ

Sembra una vita che non riesco più a trovare il tempo per scrivere due righe sul blog… Colpa del lavoro, a cui sto dedicando decisamente troppo tempo, ok, cerco di rimediare con un mini post.

Vi è mai capitato di avere la necessità di combinare due query LINQ? Sembra una baggianata (e in effetti lo è) ma io ho avuto difficoltà a trovare un esempio bello e pronto… Io ho incontrato il caso con i DomainService, ove mi è servito combinare alla query proveniente dal client, degli altri “pezzi” definiti sul server (si tratta di un evoluzione basata sui due post precedenti sui WCF Ria Services e nHibernate, di cui penso prima o poi di scrivere un post) ma la problematica è decisamente generica.

Definite due query, una che chiamerò “sorgente” è quella al cui queryProvider chiederò di eseguire il risultato della composizione e l’altra, “destinazione”, contiene i “pezzi” che voglio aggiungere alla sorgente.

Per far ciò è sufficiente far “visitare” l’espressione “destinazione” (ovvero quella da aggiungere) da un ExpressionVisitor istanziato sull’espressione sorgente e chiedere al queryProvider “sorgente” di creare una nuova query a partire dall’espressione “visitata”.

Ok, qui servirebbero pagine e pagine di spiegazione su LINQ, ma c’è tanto materiale in giro, e così ben fatto che non mi azzardo nemmeno a scrivere due righe… :-)

…Però il codice necessario lo posto subito!

Come dimostrazione ho creato una semplicissima applicazione console

 using  System;

 using  System.Collections.Generic;

 using  System.Linq;

 using  System.Text;

 

 namespace  ConsoleApplication1

 {

     class  Program 
     { 
         static  void  Main(string [] args)
         {
             List <MyEntity > myList = new  List <MyEntity >()
             {
                 new  MyEntity () 
                 {  
                     Id = 1,
                     MyProperty = "Messina" 
                 },
                 new  MyEntity ()
                 {
                     Id = 2,
                     MyProperty = "Palermo" 
                 },
                 new  MyEntity ()
                 {
                     Id=3,
                     MyProperty="Catania" 
                 }
                 new  MyEntity ()
                 {
                     Id = 4,
                     MyProperty = "Milazzo" 
                 }
             };
             //let's obtain an IQueryable... 
             var  query = myList.AsQueryable();
             //and define a query  over it 
             var  otherQuery = query.Skip(1).Take(2).OrderByDescending(x=> x.Id);
             //print out original count 
             Console .WriteLine("querCount = "  + query.Count().ToString());
             //combine the two queries... 
             var  newQuery = QueryCombiner .Combine<MyEntity >(query, otherQuery);
             //and see something about the result! 
             Console .WriteLine("newQueryCount  = "  + newQuery.Count().ToString());
             foreach  (var  item in  newQuery)
             {
                 Console .WriteLine("rimasti: "  + (item as  MyEntity ).MyProperty);
             }
             Console .ReadKey();
         }
     }
     public  class  MyEntity 
     {
         public  int  Id { get ; set ; }
         public  string  MyProperty { get ; set ; }
     }
 }



che dimostra la piccolissima classe a seguire




 using  System;

 using  System.Collections.Generic;

 using  System.Linq;

 using  System.Text;

 using  System.Collections;

 using  System.Linq.Expressions;

 using  System.ServiceModel.DomainServices.Server;

 

 namespace  ConsoleApplication1

 {

     public  class   QueryCombiner  : ExpressionVisitor 

     {

         // Methods 

         public  static  IQueryable <T> Combine<T>(IQueryable  source, IQueryable  query)

         {

             Expression  expression = Combine(source.Expression, query.Expression);

             return  source.Provider.CreateQuery<T>(expression);

         }

         public  static  IQueryable  Combine(IQueryable  source, IQueryable  query)

         {

             Expression  expression = Combine(source.Expression, query.Expression);

             return  source.Provider.CreateQuery(expression);

         }

 

         public  static  Expression  Combine(Expression  source, Expression  query)

         {

             Expression  retExp = new  Visitor (source).Visit(query);

             return  retExp;

         }

         

         //nested types 

         private  class  Visitor  : ExpressionVisitor 

         {

             // Fields 

             private  Expression  _root;

 

             // Methods 

             public  Visitor(Expression  root)

             {

                 _root = root;

             }

 

             protected  override  Expression  VisitMethodCall(MethodCallExpression  mce)

             {

                 if  (((mce.Arguments.Count > 0) 

                     && (mce.Arguments[0].NodeType == ExpressionType .Constant)) 

                     && ((((ConstantExpression ) mce.Arguments[0]).Value != null ) 

                     && (((ConstantExpression ) mce.Arguments[0]).Value is  IQueryable )))

                 {

                     List <Expression > list = new  List <Expression > {

                         _root

                     };

                     list.AddRange(mce.Arguments.Skip<Expression >(1));

                     return  Expression .Call(mce.Method, list.ToArray());

                 }

                 return  base .VisitMethodCall(mce);

             }

         }

     }

 }

 


I metodi statici in testa alla classe credo che parlino da soli… :)


Ciao a tutti e a presto (spero)


Marco

3 comments:

Anonymous said...

hi, new to the site, thanks.

Anonymous said...

why not...

Anonymous said...

Thought I would comment and say neat theme, did you make it for yourself? It's really awesome!