18

My issue is I need to query on the value of a property in a generic class. The property is tagged with an attribute.

See the following code:

 var rowKeyProperty = EFUtil.GetClassPropertyForRowKey<T>();
 var tenantKeyProperty = EFUtil.GetClassPropertyForTenantKey<T>();

 var queryResult =
                objContext.CreateObjectSet<T>().Single(l => (((int) tenantKeyProperty.GetValue(l, null)) == tenantKey) &&
                                                            (((int)rowKeyProperty.GetValue(l, null)) == KeyValue));

The rowKeyProperty and tenantKeyProperty are of type System.Reflection.PropertyInfo.

I understand why I am getting the error. When the linq query is translated to SQL, it can't understand the property.GetValue.

However, I'm completely stumped as to a work around here. Does anyone have any ideas how to achieve this? Thx.

tanguy_k
  • 11,307
  • 6
  • 54
  • 58
Ryan Z
  • 233
  • 2
  • 3
  • 5
  • 4
    The only way you'll be able to build EF-compatible queries using reflected properties is to build the expression trees yourself. – Cory Nelson Feb 28 '14 at 19:24
  • How are you determining which properties you need to get? – Servy Feb 28 '14 at 19:37
  • There is an attribute associated with the property. I call an attribute.isdefined return the property with that being true. – Ryan Z Feb 28 '14 at 20:05

4 Answers4

26

You need to actually build up the Expression objects to represent the expression that you want this to mimic, in this case the expression you want to represent is:

l => l.SomeProperty == SomeValue

So you need to build up each component of that bit by bit, from creating the parameter, defining the equality operator, the property access, the constant value, etc.

public static Expression<Func<TItem, bool>> PropertyEquals<TItem, TValue>(
    PropertyInfo property, TValue value)
{
    var param = Expression.Parameter(typeof(TItem));
    var body = Expression.Equal(Expression.Property(param, property),
        Expression.Constant(value));
    return Expression.Lambda<Func<TItem, bool>>(body, param);
}

Once you have all of that you can call it using the data that you have:

var queryResult = objContext.CreateObjectSet<T>()
    .Where(PropertyEquals<T, int>(tenantKeyProperty, tenantKey))
    .Where(PropertyEquals<T, int>(rowKeyProperty, KeyValue))
    .Single();
Servy
  • 202,030
  • 26
  • 332
  • 449
2

Appendix here... Following @Servy answer and based on this topic with a nice answer by @TomBrothers, you can use the same logic to make a StartsWith (or similar) function:

public static Expression<Func<TItem, bool>> PropertyStartsWith<TItem>(PropertyInfo propertyInfo, string value)
{
    var param = Expression.Parameter(typeof(TItem));

    var m = Expression.MakeMemberAccess(param, propertyInfo);
    var c = Expression.Constant(value, typeof(string));
    var mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
    var body = Expression.Call(m, mi, c);

    return Expression.Lambda<Func<TItem, bool>>(body, param);
}

In this case, it forces value to be a string.

Community
  • 1
  • 1
Marcos Lima
  • 761
  • 1
  • 10
  • 26
1

It is more correct to specify the type in Expression.Constant(value, typeof(TValue)))

public static Expression<Func<TItem, bool>> PropertyEquals<TItem, TValue>(
        string property, TValue value)
    {
        var xParameter = Expression.Parameter(typeof(TItem));
        var body = Expression.Equal(Expression.Property(xParameter, property), Expression.Constant(value, typeof(TValue)));
        return Expression.Lambda<Func<TItem, bool>>(body, xParameter);
    }

Or, like this, to check the property. ChangeType

public static Expression<Func<TItem, bool>> PropertyEquals<TItem, TValue>(
        string property, TValue value)
    {
        var xParameter = Expression.Parameter(typeof(TItem));
        var type = typeof(TItem).GetProperty(property).PropertyType;
        value = ChangeType<TValue>(value);
        BinaryExpression body = Expression.Equal(Expression.Property(xParameter, property), Expression.Constant(value, type));

        return Expression.Lambda<Func<TItem, bool>>(body, xParameter);
    }

What is it for. I check all class references to classes, I look for "..ID" entries. Somewhere I have a type "int" and "int?".

public class BudgetLimit : BaseRecord
{
    [Required]
    public int DepartmentID { get; set; } 
    public virtual Department Department { get; set;}

    public int? ProjectID { get; set; }
    public virtual Project Project { get; set; }
 }
Fokiruna
  • 141
  • 2
  • 4
-1

You add .AsEnableable after the LINQ statement. e.g objectdata.AsEnumerable() enter link description here

Apple Yellow
  • 307
  • 1
  • 2
  • 7