Tuesday, November 16, 2010

Converting Lambda expressions at runtime

DevExpress XPO as all modern ORM supports object querying using linq and lamda expressions. A sample query can be written as

            var xpQuery = new XPQuery<Customer>(Session.DefaultSession);

            IQueryable<Customer> customers = xpQuery.Where(customer => customer.Age == 25);

 

From the above query we can get a list of customers that are 25  years old . As you can see the code is very simple.

The problem 
When working with modularized applications you may be in context that you have access to the Customer type  only at runtime or you may want to apply that query to different object types that all have an Age property. How that can be done?

1st attempt

Lets register the customer to our module and use reflection to create the XPQuery.

            ModuleXXX moduleXxx = new ModuleXXX();

            moduleXxx.DoQuery(typeof (Customer));

 

and the DoQuery method will be something like

 

        public void DoQuery(Type customerType) {

            Type makeGenericType = typeof (XPQuery<>).MakeGenericType(customerType);

            object xpQuery = Activator.CreateInstance(makeGenericType, new object[] {Session.DefaultSession});

            //THIS IS A DEAD END

            //although we can construct the XPQuery object we have to stop here cause there is no way to use it since we not have the type at design time

        }

 2nd Attempt

Refactor our customer using an ISupportAge interface that will live in our module and use that interface with the DoQuery method

    public interface ISupportAge {

        int Age { get; set; }

    }

 

    public class Customer : BaseObject, ISupportAge {

        public Customer(Session session) : base(session) {

        }

        private int _age;

        public int Age {

            get {

                return _age;

            }

            set {

                SetPropertyValue("Age", ref _age, value);

            }

        }

    }

 

Now the DoQuery will have no arguments

 

            ModuleXXX moduleXxx = new ModuleXXX();

            moduleXxx.DoQuery();

 

and will look like

 

        public IQueryable<ISupportAge> DoQuery() {

            var xpQuery = new XPQuery<ISupportAge>(Session.DefaultSession);

            return xpQuery.Where(customer => customer.Age == 25);

        }

 

as you see in the code above in order to be able to use lamda expressions i have passed the ISupportAge interface in my XPQuery method. Too bad that XPQuery supports as generic parameters only persistent types and not interfaces

 

3rd Attempt

The previous attempt was pretty closed to what we wanted. Brainstorming and researching a little more we can find that lambda expressions are fully decomposable. That means that they consist of parts like body,parameters,sub expressions that can be decomposed and recomposed again. After decomposing the tree using Xpand.Utils.Linq.ExpressionConverter the DoQuery method now will  look similar to

 

        public IQueryable<ISupportAge> DoQuery(Type customerType) {

            //here we create a lambda based on the ISupportAge interface

            Expression<Func<ISupportAge, bool>> expression = date => date.Age == 25;

 

            //Convert the lambda using the customerType argument the new expression will be of type Expression<Func<Customer, bool>>

            //but since our method knows nothing about customer we have used the abstract Expression type.

            Expression convert = new ExpressionConverter().Convert(customerType, expression);

 

            //create the Xpquery instance

            Type xpQueryGenericType = typeof(XPQuery<>).MakeGenericType(new[] { customerType });

            object xpquery = Activator.CreateInstance(xpQueryGenericType, new[] { Session.DefaultSession });

 

            //find the where method

            MethodInfo mi = typeof(Queryable).GetMethods().Where(info => info.Name=="Where").FirstOrDefault();

            mi = mi.MakeGenericMethod(customerType);

 

            //execute the where but now using the converted lambda

            return ((IEnumerable)mi.Invoke(xpquery, new[] { xpquery, convert })).OfType<ISupportAge>().AsQueryable();

        }

 

You can find the ExpressionConverter at eXpand source code avalibale at http://expandframework.com/download.html

Subscribe to XAF feed
Subscribe to community feed

DiggIt!

0 comments:

Post a Comment