If i have a product.
var p = new Product { Price = 30 };
and i have the following linq query.
var q = repo.Products().Where(x=>x.Price == p.Price).ToList()
In an IQueryable provider, I get a MemberExpression back for the p.Price which contains a Constant Expression, however I can't seem to get the value "30" back from it.
Update
I have tried this but it doesn't seem to work.
var memberExpression = (MemberExpression)GetRootConstantExpression(m);
var fi = (PropertyInfo)memberExpression.Member;
var val = fi.GetValue(((ConstantExpression)memberExpression.Expression).Value, null);
Cheers.
You can compile and invoke a lambda expression whose body is the member access:
private object GetValue(MemberExpression member)
{
var objectMember = Expression.Convert(member, typeof(object));
var getterLambda = Expression.Lambda<Func<object>>(objectMember);
var getter = getterLambda.Compile();
return getter();
}
Local evaluation is a common technique when parsing expression trees. LINQ to SQL does this exact thing in quite a few places.
MemberExpression right = (MemberExpression)((BinaryExpression)p.Body).Right;
Expression.Lambda(right).Compile().DynamicInvoke();
The constant expression is going to point to a capture-class generated by the compiler. I've not included the decision points etc, but here's how to get 30 from that:
var p = new Product { Price = 30 };
Expression<Func<Product, bool>> predicate = x => x.Price == p.Price;
BinaryExpression eq = (BinaryExpression)predicate.Body;
MemberExpression productToPrice = (MemberExpression)eq.Right;
MemberExpression captureToProduct = (MemberExpression)productToPrice.Expression;
ConstantExpression captureConst = (ConstantExpression)captureToProduct.Expression;
object product = ((FieldInfo)captureToProduct.Member).GetValue(captureConst.Value);
object price = ((PropertyInfo)productToPrice.Member).GetValue(product, null);
price is now 30. Note that I'm assuming that Price is a property, but in reality you would write a GetValue method that handles property / field.
If you had a class:
public class Item
{
public int Id { get; set; }
}
and an instance of the object:
var myItem = new Item { Id = 7 };
You can get the value of Id using an Expression using the following code:
Expression<Func<Item, int>> exp = x => x.Id;
var me = exp.Body as MemberExpression;
var propInfo = me.Member as PropertyInfo;
var myValue = propInfo.GetValue(myItem, null);
myValue will contain "7"
As of 2020
This helper method will gracefully retrieve any expression value, without "compiling hack" :
public static object GetMemberExpressionValue (MemberExpression expression)
{
// Dependency chain of a MemberExpression is of the form:
// MemberExpression expression
// MemberExpression expression.Expression
// ... MemberExpression expression.[...].Expression
// ConstantExpression expression.[...].Expression.Expression <- base object
var dependencyChain = new List<MemberExpression>();
var pointingExpression = expression;
while (pointingExpression != null)
{
dependencyChain.Add(pointingExpression);
pointingExpression = pointingExpression.Expression as MemberExpression;
}
if (!(dependencyChain.Last().Expression is ConstantExpression baseExpression))
{
throw new Exception(
$"Last expression {dependencyChain.Last().Expression} of dependency chain of {expression} is not a constant." +
"Thus the expression value cannot be found.");
}
var resolvedValue = baseExpression.Value;
for (var i = dependencyChain.Count; i > 0; i--)
{
var expr = dependencyChain[i - 1];
resolvedValue = new PropOrField(expr.Member).GetValue(resolvedValue);
}
return resolvedValue;
}
PropOrField class :
public class PropOrField
{
public readonly MemberInfo MemberInfo;
public PropOrField (MemberInfo memberInfo)
{
if (!(memberInfo is PropertyInfo) && !(memberInfo is FieldInfo))
{
throw new Exception(
$"{nameof(memberInfo)} must either be {nameof(PropertyInfo)} or {nameof(FieldInfo)}");
}
MemberInfo = memberInfo;
}
public object GetValue (object source)
{
if (MemberInfo is PropertyInfo propertyInfo) return propertyInfo.GetValue(source);
if (MemberInfo is FieldInfo fieldInfo) return fieldInfo.GetValue(source);
return null;
}
public void SetValue (object target, object source)
{
if (MemberInfo is PropertyInfo propertyInfo) propertyInfo.SetValue(target, source);
if (MemberInfo is FieldInfo fieldInfo) fieldInfo.SetValue(target, source);
}
public Type GetMemberType ()
{
if (MemberInfo is PropertyInfo propertyInfo) return propertyInfo.PropertyType;
if (MemberInfo is FieldInfo fieldInfo) return fieldInfo.FieldType;
return null;
}
}
Using Expression.Lambda(myParameterlessExpression).Compile().Invoke() has several drawbacks:
.Compile() is slow. It can take multiple milliseconds to complete even for small expression fragments. The Invoke-call is super-fast afterwards though, takes only few nanoseconds for simple arithmetic expressions or member accesses.
.Compile() will generate (emit) MSIL code. That might sound perfect (and explains the excellent execution speed) but the problem is: That code takes up memory, which can not be freed before the application finishes, even when the GC collected the delegate-reference!
One can either avoid Compile() altogether to avoid these issues or cache the compiled delegates for re-using them. This little library of mine offers both interpretation of Expressions as well as cached compilation, where all constants and closures of the expression get replaced by additional parameters automatically, which are then re-inserted in a closure, which is returned to the user. Both processes are well-tested, used in production, both have their pros and cons against each other but are well over 100x faster than Compile() - and avoid the memory leak!
q is of type List<Product>. The List doesn't have a Price property - only the individual Products.
The first or last Product will have a price.
q.First().Price
q.Last().Price
If you know there's only one in the collection you can also flatten it using Single
q.Single().Price
Can you use the following:
var price = p.Price;
var q = repo.Products().Where(x=>x.Price == price).ToList()
And what exactly are you trying to accomplish?
Because to access the value of Price, you'd have to do something like:
var valueOfPrice = q[0].Price;
Related
I'm wondering if is it possible to make the "property name" dynamic in the filter expressions
Consider scenario
List<Person> GetPerson(int countryID, int stateID, int cityID, int zip)
{
//List of person can be filtered based on below line of code
List<Person> filteredPersons= persons.FindAll(rule => rule.CountryID == countryID).ToList();
//is it possible to specify ".Country" dynamically. something like
List<Person> filteredPersons= persons.FindAll(rule => rule."propertyName"== countryID).ToList();
}
Considering your example one method you could employ is using the .Where() extension instead of FindAll() this could then allow you to build your expression manually. A quick example would be as below.
static List<Person> GetPerson(int countryID, int stateID, int cityID, int zip)
{
//create a new expression for the type of person this.
var paramExpr = Expression.Parameter(typeof(Person));
//next we create a property expression based on the property named "CountryID" (this is case sensitive)
var property = Expression.Property(paramExpr, "CountryID");
//next we create a constant express based on the country id passed in.
var constant = Expression.Constant(countryID);
//next we create an "Equals" express where property equals containt. ie. ".CountryId" = 1
var idEqualsExpr = Expression.Equal(property, constant);
//next we convert the expression into a lamba expression
var lExpr = Expression.Lambda<Func<Person, bool>>(idEqualsExpr, paramExpr);
//finally we query our dataset
return persons.AsQueryable().Where(lExpr).ToList();
}
So this looks like alot of code but what we have basically done is manually constructed the expression tree with the end result looking similar to (and functioning as)
return persons.AsQueryable().Where(p => p.CountryId = countryId);
Now we can take this forward lets say you wanted to query for multiple properties using an and\or based on the method call. Ie you could change all your "filter" paramters to be Nullable and check if a value is passed in we filter for it such as.
static List<Person> GetPerson(int? countryID = null, int? stateID = null, int? cityID = null, int? zip = null)
{
//create a new expression for the type of person this.
var paramExpr = Expression.Parameter(typeof(Person));
//var equalExpression = Expression.Empty();
BinaryExpression equalExpression = null;
if (countryID.HasValue)
{
var e = BuildExpression(paramExpr, "CountryId", countryID.Value);
if (equalExpression == null)
equalExpression = e;
else
equalExpression = Expression.And(equalExpression, e);
}
if (stateID.HasValue)
{
var e = BuildExpression(paramExpr, "StateID", stateID.Value);
if (equalExpression == null)
equalExpression = e;
else
equalExpression = Expression.And(equalExpression, e);
}
if (equalExpression == null)
{
return new List<Person>();
}
//next we convert the expression into a lamba expression
var lExpr = Expression.Lambda<Func<Person, bool>>(equalExpression, paramExpr);
//finally we query our dataset
return persons.AsQueryable().Where(lExpr).ToList();
}
static BinaryExpression BuildExpression(Expression expression, string propertyName, object value)
{
//next we create a property expression based on the property named "CountryID" (this is case sensitive)
var property = Expression.Property(expression, propertyName);
//next we create a constant express based on the country id passed in.
var constant = Expression.Constant(value);
//next we create an "Equals" express where property equals containt. ie. ".CountryId" = 1
return Expression.Equal(property, constant);
}
Now this is a bit more code but as you can see we are now accepting null values for all our parameters and building our query for additional properties.
Now you could take this further (as assuming a more generic method is required) of passing in a Dictionary<string, object> of the properties \ values to query. This can be done as an extension method on an IEnumerable<T> as listed below.
public static class LinqExtensions
{
public static IEnumerable<T> CustomParameterQuery<T>(this IEnumerable<T> entities, Dictionary<string, object> queryVars)
{
if (entities.Count() == 0 || queryVars.Count == 0)
{
return entities;
}
//create a new expression for the type of person this.
var paramExpr = Expression.Parameter(typeof(T));
BinaryExpression equalExpression = null;
foreach (var kvp in queryVars)
{
var e = BuildExpression(paramExpr, kvp.Key, kvp.Value);
if (equalExpression == null)
equalExpression = e;
else
equalExpression = Expression.And(equalExpression, e);
}
if (equalExpression == null)
{
return new T[0];
}
//next we convert the expression into a lamba expression
var lExpr = Expression.Lambda<Func<T, bool>>(equalExpression, paramExpr);
//finally we query our dataset
return entities.AsQueryable().Where(lExpr);
}
static BinaryExpression BuildExpression(Expression expression, string propertyName, object value)
{
//next we create a property expression based on the property name
var property = Expression.Property(expression, propertyName);
//next we create a constant express based on the country id passed in.
var constant = Expression.Constant(value);
//next we create an "Equals" express where property equals containt. ie. ".CountryId" = 1
return Expression.Equal(property, constant);
}
}
Now this could be easily called as:
var dict = new Dictionary<string, object>
{
{ "CountryID", 1 },
{ "StateID", 2 }
};
var e = persons.CustomParameterQuery(dict);
Now obiously this is not a perfect example, but should get you moving in the right direction. Now you "could" also support "OR" statements etc by using Expression.Or instead of Expression.And when combining expressions.
I Must add this is very error prone as it requires the Property names to be exact to the entity, you could use reflection on T and determine if the PropertyName exists and If it is in the correct casing.
Search for System.Linq.Dynamic in NuGet. That's the easiest way to start with Dynamic Linq.
Expressions are not really identic, but should be. They differ in one slight details. I am quite new to Expressions, but I think this could be confusing even for quite experienced player. I refactored the code which processes some data to make Expression used as parameter for IQueryable.Where(). It is functionally equivalent as far as I can see.
I have here former code, which worked well, and generates perfectly functional Expression:
private Expression<Func<T, bool>> StringPropertyContains<T>(string propertyName, string value)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentNullException(nameof(propertyName));
}
var param = Expression.Parameter(typeof(T));
MemberExpression member = null;
if (propertyName.Contains('/'))
{
var splittedPropertyName = propertyName.Split('/');
var propertyInfo = this.GetPropertyInfo(typeof(T), splittedPropertyName.First());
member = Expression.MakeMemberAccess(param, propertyInfo);
for (int i = 1; i < splittedPropertyName.Length; i++)
{
if (propertyInfo.PropertyType.IsInterface)
{
//specifically for IActorWithExtraDetails -> reason to refactor
if (typeof(IActor).IsAssignableFrom(propertyInfo.PropertyType) && typeof(IActor).GetProperties().FirstOrDefault(pi => pi.Name.Equals(splittedPropertyName[i], StringComparison.OrdinalIgnoreCase)) != null)
{
propertyInfo = this.GetPropertyInfo(typeof(IActor), splittedPropertyName[i]);
}
else
{
propertyInfo = this.GetPropertyInfo(propertyInfo.PropertyType, splittedPropertyName[i]);
}
}
else
{
propertyInfo = this.GetPropertyInfo(propertyInfo.PropertyType, splittedPropertyName[i]);
}
}
member = Expression.MakeMemberAccess(member, propertyInfo);
}
else
{
var propertyInfo = this.GetPropertyInfo(typeof(T), propertyName);
member = Expression.MakeMemberAccess(param, propertyInfo);
}
var constant = Expression.Constant(value, typeof(string));
var methodInfo = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
var body = Expression.Call(member, methodInfo, constant);
return Expression.Lambda<Func<T, bool>>(body, param);
}
This is how it looks in DebugView property of the IQueryable:
.Lambda #Lambda2<System.Func`2[AccessManagement.Model.Application,System.Boolean]>(AccessManagement.Model.Application $var1)
{
.Call ($var1.Name).Contains("hive")
}
Here is new, refactored code moved to own method:
private Expression<Func<T, bool>> StringPropertyContains<T>(string propertyName, string value)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentNullException(nameof(propertyName));
}
var param = Expression.Parameter(typeof(T));
MemberExpression member = this.GetMemberExpression(typeof(T), propertyName.Trim('/').Split('/'));
var constant = Expression.Constant(value, typeof(string));
var methodInfo = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
var body = Expression.Call(member, methodInfo, constant);
return Expression.Lambda<Func<T, bool>>(body, param);
}
private MemberExpression GetMemberExpression(Type baseType, string[] path)
{
MemberExpression result = null;
Type type = baseType;
PropertyInfo propertyInfo = null;
foreach (string segment in path)
{
//if type is interface, just spray and pray
if (type.IsInterface)
{
propertyInfo = this.GetDescendantProperties(type)
.FirstOrDefault(pi => pi.Name.Equals(segment, StringComparison.OrdinalIgnoreCase));
}
else
{
propertyInfo = this.GetPropertyInfo(type, segment);
}
if (propertyInfo == null)
{
throw new ArgumentNullException(nameof(propertyInfo));
}
result =
result == null ?
Expression.MakeMemberAccess(Expression.Parameter(baseType), propertyInfo) :
Expression.MakeMemberAccess(result, propertyInfo);
}
return result;
}
This is how expression from refactored method looks like in DebugView:
.Lambda #Lambda2<System.Func`2[AccessManagement.Model.Application,System.Boolean]>(AccessManagement.Model.Application $var1)
{
.Call ($var2.Name).Contains("hive")
}
There is only one difference. As you can see, there is $var2 in second case, not $var1. This variable doesn't exist in whole expression tree. I have no idea why, but I'd bet that is the issue, because everything else remains same. Only other difference is that within second case Expression processing is something cachced in member.RuntimeMethodInfo.base.m_cachedData (debug view path).
In your first code snippet, you're declaring a parameter:
var param = Expression.Parameter(typeof(T));
This is used as the lambda parameter, and is used in the code here:
Expression.MakeMemberAccess(param, propertyInfo);
(this is called twice actually). So the code makes use of the parameter passed to the lambda.
In your second code snippet, you're still using param as the parameter to the lambda, but then you don't use it anywhere in the lambda body.
You're calling this instead:
Expression.MakeMemberAccess(Expression.Parameter(baseType), propertyInfo)
That Expression.Parameter(baseType) creates a second and unrelated variable, which never gets an actual value assigned to it. You should have used the param reference here.
That's where $var2 comes from. $var1 is the param reference.
Consider using the Expression.Parameter(Type, string) overload next time, which lets you name your parameters for debugging purposes. It'll be easier to reason about.
I'm creating a method that receives a Queryable<T> source, a string with a property name/path (could be a deep property for example "TrParent.DataTypes" to achieve this x => x.TrParent.DataTypes) and Enumerable<int> which holds the values I need to intersect.
Basically I come from the need to create the following query dynamically (I mean <DT_Det_Tr> and TrParent.DataTypes being know only at runtime, in the example DT_Det_Tr is not a type it is a class):
var _vals = new List<int>();
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any()
);
Please keep in mind that the preceding query is just an example of what I need to achieve dynamically, what I really need is an expression tree that creates a predicate like the one shown above but using a dynamic type and with the deep navigation property specified within a string.
So, I'm using this function to create the expression for the deep property:
private static LambdaExpression CreateDelegateExpression<T>(out Type resultingtype, string property, string parameterName = "x")
{
var type = typeof(T);
ParameterExpression param = Expression.Parameter(type, parameterName);
Expression expr = param;
foreach (string prop in property.Split('.'))
{
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, param);
resultingtype = type;
return lambda;
}
And here is what I have so far for my function:
public static IQueryable<T> Intersect<T>(this IQueryable<T> source, string property, IEnumerable<int> value)
{
//List of ids
var _value = Expression.Constant(value);
//Get delegate expression to the deep property and it's inner type
Type type = null;
var lambda = CreateDelegateExpression<T>(out type, property, "x");
var enumtype = type.GetGenericArguments()[0];
ParameterExpression tpe = Expression.Parameter(enumtype, "y");
Expression propExp = Expression.Property(tpe, enumtype.GetProperty("Id"));
MethodInfo innermethod = typeof(Queryable).GetMethods().Where(x => x.Name == "Select").First();
//Error on next line...
var selectCall = Expression.Call(typeof(Queryable),
"Select",
new Type[] { enumtype, typeof(long) },
lambda,
propExp);
//TODO: Add rest of logic and actually filter the source
return source;
}
In the var selectCall = line I'm getting error:
No generic method 'Select' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.
I've read a lot here on SO and other sites but I can't get past this part, I feel I'm going to bump into more trouble when I get to the .Intersect(List<int>).Any() part so any help on that also would be grand, thanks.
After a lot of thought, investigation and attempts I came up with a solution.
First, I made a simpler version of my goal query (the static example I used in my question), so instead of:
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any()
);
I made this:
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Any(y => _vals.Contains(y.Id))
);
Which is a lot easier to translate to expressions (or at least it was for me) because it omits the Select call.
I got rid of the method I was using to create the deep navigation property expression and streamlined it in my Intersect function, this was because it was doing some work I don't really need here plus I needed access to some of the variables I use inside it, then I made this:
public static IQueryable<T> Intersect<T>(this IQueryable<T> source, string property, IEnumerable<int> value)
{
var type = typeof(T);
var _value = Expression.Constant(value); //List of ids
//Declare parameter for outer lambda
ParameterExpression param = Expression.Parameter(type, "x");
//Outer Lambda
Expression expr = param;
foreach (string prop in property.Split('.')) //Dig for deep property
{
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
//Get deep property's type
var enumtype = type.GetGenericArguments()[0];
//Declare parameter for inner lambda
ParameterExpression tpe = Expression.Parameter(enumtype, "y");
//Inner Collection lambda logic
//Property for inner lambda
Expression propExp = Expression.Property(tpe, enumtype.GetProperty("Id"));
//Contains method call .Contains(y.Id)
var containsMethodExp = Expression.Call(typeof(Enumerable), "Contains", new[] { propExp.Type }, _value, propExp);
//Create Expression<Func<enumtype, bool>>
var innerDelegateType = typeof(Func<,>).MakeGenericType(enumtype, typeof(bool));
//Create Inner lambda y => _vals.Contains(y.Id)
var innerFunction = Expression.Lambda(innerDelegateType, containsMethodExp, tpe);
//Get Any method info
var anyMethod = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(enumtype);
//Call Any with inner function .Any(y => _vals.Contains(y.Id))
var outerFunction = Expression.Call(anyMethod, expr, innerFunction);
//Call Where
MethodCallExpression whereCallExpression = Expression.Call
(
typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda<Func<T, bool>>(outerFunction, new ParameterExpression[] { param })
);
//Create and return query
return source.Provider.CreateQuery<T>(whereCallExpression);
}
I hope this helps anyone trying to develop a similar solution.
Working with expression trees can be very hard and frustrating at first, but it's a really powerful tool once you get the hold of it.
If you have access to the dynamic keyword from c# 4.0, you might be able to work around the problem like this:
var _vals = new List<int>();
var res = dbContext.Set<DT_Det_Tr>()
.Where(obj => { dynamic x = obj;
return x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any();
}
);
But I don't know enough about the details of the problem you want to solve to say for sure.
Given a primitive value age I know how to create an expression like this:
//assuming: age is an int or some other primitive type
employee => employee.Age == age
By doing this:
var entityType = typeof(Employee);
var propertyName = "Age";
int age = 30;
var parameter = Expression.Parameter(entityType, "entity");
var lambda = Expression.Lambda(
Expression.Equal(
Expression.Property(parameter, propertyName),
Expression.Constant(age)
)
, parameter);
That works fine except in scenarios where the property and constant in question are not primitive types.
How would I construct a similar expression if the comparison is between objects?
With EF I can just write:
Location location = GetCurrentLocation();
employees = DataContext.Employees.Where(e => e.Location == location);
That also works, but if I try to create the same expression:
var entityType = typeof(Employee);
var propertyName = "Location";
var location = GetCurrentLocation();
var parameter = Expression.Parameter(entityType, "entity");
var lambda = Expression.Lambda(
Expression.Equal(
Expression.Property(parameter, propertyName),
Expression.Constant(location)
)
, parameter);
I get an error that says:
Unable to create a constant value of type 'Location'. Only primitive types or enumeration types are supported in this context.
My suspicion is that Expression.Constant() only expects primitive types, so I need to use a different expression factory method. (maype Expression.Object? - I know that doesn't exist)
Is there a way to create an expression that compares objects? Why is that EF is able to interpret it correctly if its a compiled LINQ statement, but not when it is an expression?
In addition to what has been mentioned in previous answers. A more specific solution would go as such:
public static Expression CreateExpression<T>(string propertyName, object valueToCompare)
{
// get the type of entity
var entityType = typeof(T);
// get the type of the value object
var valueType = valueToCompare.GetType();
var entityProperty = entityType.GetProperty(propertyName);
var propertyType = entityProperty.PropertyType;
// Expression: "entity"
var parameter = Expression.Parameter(entityType, "entity");
// check if the property type is a value type
// only value types work
if (propertyType.IsValueType || propertyType.Equals(typeof(string)))
{
// Expression: entity.Property == value
return Expression.Equal(
Expression.Property(parameter, entityProperty),
Expression.Constant(valueToCompare)
);
}
// if not, then use the key
else
{
// get the key property
var keyProperty = propertyType.GetProperties().FirstOrDefault(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Length > 0);
// Expression: entity.Property.Key == value.Key
return Expression.Equal(
Expression.Property(
Expression.Property(parameter, entityProperty),
keyProperty
),
Expression.Constant(
keyProperty.GetValue(valueToCompare),
keyProperty.PropertyType
)
);
}
}
IMPORTANT POINTS :
Make sure to check for nulls
Make sure propertyType and valueType are compatible (either they are the same type or are convertible)
Several assumptions are made here (e.g. that you do assign a KeyAttribute)
This code is not tested, so it is not exactly copy/paste ready.
Hope that helps.
You can't do that because EF doesn't know how to translate equality comparisons on Location into a SQL expression.
However, if you know what properties of Location you want to compare, you can do this with anonymous types:
var location = GetCurrentLocation();
var locationObj = new { location.LocationName, location.LocationDescription };
employees = DataContext.Employees.Where(e => new { e.Location.LocationName, e.Location.Description } == locationObj);
Of course that's equivalent to:
var location = GetCurrentLocation();
employees = DataContext.Employees.Where(e => e.Location.LocationName == location.Name &&
e.Location.Description == location.Description);
Give the code below a run. I wanted to test your assumption that e => e.Location == location is compiling into something that can be constructed with Expression.Equal, Expression.Property, and Expression.Constant.
class Program {
static void Main(string[] args) {
var location = new Location();
Expression<Func<Employee, bool>> expression = e => e.Location == location;
var untypedBody = expression.Body;
//The untyped body is a BinaryExpression
Debug.Assert(
typeof(BinaryExpression).IsAssignableFrom(untypedBody.GetType()),
"Not Expression.Equal");
var body = (BinaryExpression)untypedBody;
var untypedLeft = body.Left;
var untypedRight = body.Right;
//The untyped left expression is a MemberExpression
Debug.Assert(
typeof(MemberExpression).IsAssignableFrom(untypedLeft.GetType()),
"Not Expression.Property");
////The untyped right expression is a ConstantExpression
//Debug.Assert(
// typeof(ConstantExpression).IsAssignableFrom(untypedRight.GetType()),
// "Not Expression.Constant");
//The untyped right expression is a MemberExpression?
Debug.Assert(
typeof(MemberExpression).IsAssignableFrom(untypedRight.GetType())));
}
}
public class Employee
{
public Location Location { get; set; }
}
public class Location { }
It seems like it isn't, and its because the right expression isn't a Constant. To see this, uncomment the commented out code.
What I don't understand is why the right expression is a MemberExpression. Perhaps someone who knows the linq expression compiler can shed more light onto this then I can.
Edit: This may have to do with closure in lambdas - a class is created behind the scenes which contains the closed over variables. The location might then be a member of that class. I'm not sure about this, but it's what I suspect.
This post may shed additional light on the situation.
Background:
I have a web service that returns the rows in a table (table name supplied as a parameter) with Ids greater than a certain Id (also supplied as a parameter). We are assuming the Ids are sequential.
I am using Linq to SQL for database interaction so I want to return the new rows as:
List<WhateverObject>
Because we only know the table name at runtime, I can't use Linq in a normal fashion which has made things much more complex.
Question:
The code is below (and it works). How can I simplify it? It seems overly complex.
private object GetUpdateList(string tableName, int Id, DataClassesDataContext db)
{
PropertyInfo pi = db.GetType().GetProperty(tableName);
var table = pi.GetValue(db, null);
// Get type of object within the table.
Type genericType = table.GetType().GetGenericArguments()[0];
// The Where method lives on the Enumerable type in System.Linq
var whereMethods = typeof(System.Linq.Enumerable)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(mi => mi.Name == "Where");
// There are actually 2 where methods - we want the one with 2 parameters
MethodInfo whereMethod = null;
foreach (var methodInfo in whereMethods)
{
var paramType = methodInfo.GetParameters()[1].ParameterType;
if (paramType.GetGenericArguments().Count() == 2)
{
// we are looking for Func<TSource, bool>, the other has 3
whereMethod = methodInfo;
break;
}
}
Func<object, bool> IdEquals = BuildEqFuncFor("Id", Id);
whereMethod = whereMethod.MakeGenericMethod(genericType);
var result = whereMethod.Invoke(table, new object[] { table, IdEquals });
MethodInfo toListMethod = typeof(System.Linq.Enumerable).GetMethod("ToList").MakeGenericMethod(genericType);
return toListMethod.Invoke(result, new object[] { result });
}
// Build lambda expression for use in Linq
private static Func<object, bool> BuildEqFuncFor(string prop, object val)
{
// We know we are comparing integers here so cast them.
// There is probably a more general solution.
return t => (int)t.GetType().InvokeMember(prop, BindingFlags.GetProperty, null, t, null) > (int)val;
}
To come up with this solution I have had to reference the following questions:
How do I invoke an extension method using reflection?
How to call extension method "ElementAt"of List<T> with reflection?
How to get the type of T from a member of a generic class or method?
Convert func to predicate using reflection in C#
Try something like this:
private IList GetUpdateList(string tableName, int id, DataClassesDataContext db)
{
System.Reflection.PropertyInfo pi = db.GetType().GetProperty(tableName);
var table = pi.GetValue(db, null);
// Get type of object within the table.
Type genericType = table.GetType().GetGenericArguments()[0];
var param = Expression.Parameter(genericType, "x");
var predicateExpr = Expression.Lambda(
Expression.GreaterThan(
Expression.Property(param, "Id"),
Expression.Constant(id)),
param);
return this
.GetType()
.GetMethod("GetUpdateListGeneric")
.MakeGenericMethod(genericType)
.Invoke(this, new[] { table, predicateExpr }) as IList;
}
private IList<T> GetUpdateListGeneric<T>(
Table<T> table,
Expression<Func<T, bool>> predicate) where T : class
{
return table.Where(predicate).ToList();
}