3

I want to create lambda something like this

user => user.Address == address

but is not compiled one, I want to return LambdaExpression

If the lambda take constant like this

user => user.Age == 50

Then I can use this method

    public static LambdaExpression PropertyEqual(Type tEntityType, string propertyName, object value)
    {
        // entity => entity.PropName == const
        var itemParameter = Expression.Parameter(tEntityType, "entity");
        return Expression.Lambda
        (
            Expression.Equal
            (
                Expression.Property
                (
                    itemParameter,
                    propertyName
                ),
                Expression.Constant(value) // Tried to replace this with Expression.Parameter or Expression.Variable but no luck
            ),
            new[] { itemParameter }
        );
    }

How to make this method accept variable address come from the scope just outside from the lambda expression?

var addressPropertyName = "Address";
var address = new Address() {...};
var q = Repo.GetQuery().Where(PropertyEqual(typeof(User), addressPropertyName, address))

Edit: clarify my question: How build the right Expression to generate the first lambda?

Update: This is not possible because EF does not support non-scalar variable

I change the lambda to user => user.AddressId == addressId as suggested here. It just the matter how to get AddressId FK PropertyInfo from a known navigation property Address.

Community
  • 1
  • 1
CallMeLaNN
  • 8,328
  • 7
  • 59
  • 74

1 Answers1

4

You can't dynamically generate a closure on a variable (you can't extend the lifetime of a variable outside its context) because this is a trick of the compiler (that rewrites your code to do it).

If you don't want a closure but you want an additional parameter then you can add an additional parameter to the expression.

You could

Expression<Func<string>> myExpr = () => address;

now you have an expression that closes around your address. Now you only have to combine the two expressions.

You'll have to change the method to:

public static LambdaExpression PropertyEqual<T>(Type tEntityType, string propertyName, Expression<Func<T>> getValue)
{
   // entity => entity.PropName == const
   var itemParameter = Expression.Parameter(tEntityType, "entity");
   return Expression.Lambda
   (
       Expression.Equal
       (
           Expression.Property
           (
               itemParameter,
               propertyName
           ),
           Expression.Invoke(getValue) // You could directly use getValue.Body instead of Expression.Invoke(getValue)
       ),
       new[] { itemParameter }
   );
}
xanatos
  • 109,618
  • 12
  • 197
  • 280
  • adding additional parameter will turn into something like `(user, address) => user.Address == address` instead of `user => user.Address == address` which has different return expression `Expression>` instead of `Expression>` – CallMeLaNN Aug 01 '13 at 10:16
  • 1
    You can't get a "reference" to a variable and put it in an `Expression`. `TypedReference` and expression trees aren't "compatible". What you can do is pre-create an expression that points to your address and then compose the two expression trees. The closure will be done by the compiler. – xanatos Aug 01 '13 at 10:19
  • Sorry, how to combine both expressions and return `LambdaExpression`? `Expression.Lambda(...)` accept only one `Expression` body. – CallMeLaNN Aug 01 '13 at 10:23
  • I got "`Invoke` is not supported in LINQ to Entities" & if using getValue.Body I got error "Unable to create a constant value...". I think because the `address` variable is an EF entity instead of value type. – CallMeLaNN Aug 01 '13 at 10:43
  • I think `getValue.Body` is the right way to go since `Expression.Invoke` require to use LinqKit `IQueryable.AsExpandable()`. Anyway I still got "Unable to create a constant value 'T'. Only primitive types or enumeration types are supported in this context" the same error when using `Expression.Constant()`. So I think variable scope is not an issue but this is [limitation of EF](http://msdn.microsoft.com/en-us/library/bb896317.aspx#RefNonScalarClosures). I plan to change into `user => user.AddressId == addressId` but I need to find out how to get the FK prop from navigation prop. – CallMeLaNN Aug 01 '13 at 12:59
  • @CallMeLaNN What type exactly is `address`? – xanatos Aug 01 '13 at 13:01
  • It is an `Address` entity and `user.Address` is a navigation property. – CallMeLaNN Aug 01 '13 at 13:08
  • 1
    @CallMeLaNN Then yes, I think your problem is that you can't directly do it (as the link you provided say). You could compare their `.ID`. – xanatos Aug 01 '13 at 13:11