1

I have this LINQ Expression that finds all the historical changes to a given customer's CreditBalance:

var history = GetHistory(id);
var changes = history.Where(x => history.Where(y => y.AuditId < x.AuditId)
                                        .OrderByDescending(y => y.AuditId)
                                        .Select(y => y.CreditBalance)
                                        .FirstOrDefault() != x.CreditBalance);

This function works as expected. What I want to do is change this function to allow the user to query changes to any historical field. The way I chose to tackle this was with expression trees.

So far I have come up with this solution:

var history = GetHistory(id);
var c = Expression.Parameter(typeof(Customer_history), "c");
var d = Expression.Parameter(typeof(Customer_history), "d");
var cAudit = Expression.Property(c, typeof(Customer_history).GetProperty("AuditId"));
var dAudit = Expression.Property(d, typeof(Customer_history).GetProperty("AuditId"));

var whereBody = Expression.LessThan(dAudit, cAudit);
var whereLambda = Expression.Lambda(whereBody, d);
var where = Methods.QueryableWhere.MakeGenericMethod(typeof(Customer_history));

var whereCall = Expression.Call(null, where, **Expression.Constant(history)**, whereLambda);

var orderByLambda = Expression.Lambda(dAudit, d);

var orderBy = Methods.QueryableOrderByDescending.MakeGenericMethod(typeof(Customer_history), orderByLambda.Body.Type);
var orderByCall = Expression.Call(null, orderBy, whereCall, orderByLambda);

var dProp = Expression.Property(d, typeof(Customer_history).GetProperty(field));
var selectLambda = Expression.Lambda(dProp, d);

var select = Methods.QueryableSelect.MakeGenericMethod(typeof(Customer_history), selectLambda.Body.Type);
var selectCall = Expression.Call(null, select, orderByCall, selectLambda);

var firstOrDefault = Methods.QueryableFirstOrDefault.MakeGenericMethod(selectLambda.Body.Type);
var firstOrDefaultCall = Expression.Call(null, firstOrDefault, selectCall);

var cProp = Expression.Property(c, typeof(Customer_history).GetProperty(field));
var comparison = Expression.NotEqual(firstOrDefaultCall, cProp);
var lambda = Expression.Lambda<Func<Customer_history, bool>>(comparison, c);

var changes = history.Where(lambda);

The problem is, I get this exception when the query is executed:

Unable to create constant value of type 'Namespace.Customer_history'. Only primitive types or enumeration types are supported in this context.

Now I am assuming that the issue is the Expression.Constant(history) statement based on the exception message. The problem is, I don't know how to rewrite it to allow the query provider to handle it appropriately. I know it works because of the original query, I just don't know how to do it in an expression tree.

Can anyone provide any direction?

2 Answers 2

1

As suspected, it seems that a ConstantExpression is not the way to obtain the value from a local variable.

What I needed to do was create a private class to store the variable in, and then I was able to access it with a field MemberExpression

private class ValueHolder<T>
{
      public IQueryable<T> History;
}

Then in my method I was able to have the expression evaluated using this:

var valueHolder = new ValueHolder<T>
{
      History = data
};

var c = Expression.Parameter(typeof(T), "c");
var constantEx = Expression.Constant(valueHolder);
var fieldEx = Expression.Field(constantEx, valueHolder.GetType().GetField("History"));
Sign up to request clarification or add additional context in comments.

Comments

0

You can always try using dynamic linq, which allows you to use strings as expressions instead of lambda.

Example:

var query = history.Where("MyField = MyFilter");

https://www.nuget.org/packages/System.Linq.Dynamic.Library/

https://github.com/NArnott/System.Linq.Dynamic

1 Comment

How does that allow you to access a local variable from the condition?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.