Ben Collins
Ben Collins

Reputation: 20686

Binding DefaultExpression to property in Linq-to-Entities query

I have a class that helps project entities to POCOs by emitting Linq expressions that can be used with an IQueryable. Here is an expression I'm having some trouble with:

result = Expression.Bind(dProperty,
                         Expression.Coalesce(Expression.Property(parameterExpression, sProperty),
                                             Expression.Default(dType)));  

In this statement, dProperty is a PropertyInfo representing the destination property in the binding, dType is the Type of that property, parameterExpression is another Linq expression representing the parameter of type TEntity, and sProperty is a PropertyInfo object representing the source property.

This builds, but at runtime when the Entity Framework expression visitor implementation works its way through the tree, it sees this and throws a "not supported" exception (below). What seems to be the meaning is that the DefaultExpression I'm using as the right side of the coalescence operator expression isn't supported in EF. I tried searching quite a bit for documentation or discussions that addressed this and couldn't find anything (of course, it's hard to do a very good search on a word like 'default'). Anyway, is it true that the C# 'default' operator is not supported in Linq-to-Entities? If it is true, then how do I do what I'm trying to do, which is to assign a property from a Nullable whose generic argument type is the same as the destination property?

In other words, the linq expression would look like this if you wrote it as a lambda:

(/*long*/dest, /*long?*/src) => dest = src ?? default(long)

Here's the exception.

System.NotSupportedException: Unknown LINQ expression of type 'Default'.
   at System.Linq.Expressions.EntityExpressionVisitor.Visit(Expression exp)
   at System.Data.Objects.ELinq.Funcletizer.<>c__DisplayClass5.<Nominate>b__4(Expression exp, Func`2 baseVisit)
   at System.Linq.Expressions.EntityExpressionVisitor.BasicExpressionVisitor.Visit(Expression exp)
   at System.Linq.Expressions.EntityExpressionVisitor.VisitBinary(BinaryExpression b)
   at System.Data.Objects.ELinq.Funcletizer.<>c__DisplayClass5.<Nominate>b__4(Expression exp, Func`2 baseVisit)
   at System.Linq.Expressions.EntityExpressionVisitor.BasicExpressionVisitor.Visit(Expression exp)
   at System.Linq.Expressions.EntityExpressionVisitor.VisitMemberAssignment(MemberAssignment assignment)
   at System.Linq.Expressions.EntityExpressionVisitor.VisitBinding(MemberBinding binding)
   at System.Linq.Expressions.EntityExpressionVisitor.VisitBindingList(ReadOnlyCollection`1 original)
   at System.Linq.Expressions.EntityExpressionVisitor.VisitMemberInit(MemberInitExpression init)
   at System.Data.Objects.ELinq.Funcletizer.<>c__DisplayClass5.<Nominate>b__4(Expression exp, Func`2 baseVisit)
   at System.Linq.Expressions.EntityExpressionVisitor.BasicExpressionVisitor.Visit(Expression exp)
   at System.Linq.Expressions.EntityExpressionVisitor.VisitExpressionList(ReadOnlyCollection`1 original)
   at System.Linq.Expressions.EntityExpressionVisitor.VisitInvocation(InvocationExpression iv)
   at System.Data.Objects.ELinq.Funcletizer.<>c__DisplayClass5.<Nominate>b__4(Expression exp, Func`2 baseVisit)
   at System.Linq.Expressions.EntityExpressionVisitor.BasicExpressionVisitor.Visit(Expression exp)
   at System.Linq.Expressions.EntityExpressionVisitor.VisitLambda(LambdaExpression lambda)
   at System.Data.Objects.ELinq.Funcletizer.<>c__DisplayClass5.<Nominate>b__4(Expression exp, Func`2 baseVisit)
   at System.Linq.Expressions.EntityExpressionVisitor.BasicExpressionVisitor.Visit(Expression exp)
   at System.Linq.Expressions.EntityExpressionVisitor.VisitUnary(UnaryExpression u)
   at System.Data.Objects.ELinq.Funcletizer.<>c__DisplayClass5.<Nominate>b__4(Expression exp, Func`2 baseVisit)
   at System.Linq.Expressions.EntityExpressionVisitor.BasicExpressionVisitor.Visit(Expression exp)
   at System.Linq.Expressions.EntityExpressionVisitor.VisitExpressionList(ReadOnlyCollection`1 original)
   at System.Linq.Expressions.EntityExpressionVisitor.VisitMethodCall(MethodCallExpression m)
   at System.Data.Objects.ELinq.Funcletizer.<>c__DisplayClass5.<Nominate>b__4(Expression exp, Func`2 baseVisit)
   at System.Linq.Expressions.EntityExpressionVisitor.BasicExpressionVisitor.Visit(Expression exp)
   at System.Data.Objects.ELinq.Funcletizer.Nominate(Expression expression, Func`2 localCriterion)
   at System.Data.Objects.ELinq.Funcletizer.Funcletize(Expression expression, Func`1& recompileRequired)
   at System.Data.Objects.ELinq.ExpressionConverter..ctor(Funcletizer funcletizer, Expression expression)
   at System.Data.Objects.ELinq.ELinqQueryState.CreateExpressionConverter()
   at System.Data.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
   at System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
   at System.Data.Objects.ObjectQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
   at System.Linq.Enumerable.<SelectManyIterator>d__14`2.MoveNext()
   at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
   at System.Linq.Enumerable.<TakeIterator>d__3a`1.MoveNext()
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at System.Collections.Generic.List`1.InsertRange(Int32 index, IEnumerable`1 collection)
   at MyClass...

Upvotes: 2

Views: 1171

Answers (1)

casperOne
casperOne

Reputation: 74530

As you note, LINQ-to-Entities does not support the Expression.Default method.

However, LINQ-to-Entities does support the Expression.Constant method.

If you are working with a generic type parameter, you can simply set the second parameter of the coalesce operation be a constant; after all, default(T) is going to return a constant given the same instance of T.

The code to do that is as follows:

result = Expression.Bind(dProperty, 
    Expression.Coalesce(
        Expression.Property(parameterExpression, sProperty),
        Expression.Constant(default(T), dType),
    )
);

I'm assuming you aren't working with generics, so you can't use the default keyword here. However, you can create a method that will get it for you given a Type.

(Note, if you don't want to rely on a solution based on the codification of rules outside the code, you can get the result of Expression.Default on-the-fly with only a Type instance.)

Once you have that, you can do the following:

result = Expression.Bind(dProperty, 
    Expression.Coalesce(
        Expression.Property(parameterExpression, sProperty),
        // Note: The example referenced was changed to an extension method
        // on Type.
        Expression.Constant(dType.GetDefault(), dType),
    )
);

Upvotes: 4

Related Questions