I'm trying to build something like conditional queries to get only needed data from the underlying database.
Currently I have the following query (which works fine)
var eventData = dbContext.Event.Select(t => new
{
Address = true ? new AnonymousEventGetAddress
{
AddressLine1 = t.Address.AddressLine1,
CityName = t.Address.AddressCityName
} : new AnonymousEventGetAddress(),
});
If I change it to
var includeAddress = true; // this will normally be passed as param
var eventData = dbContext.Event.Select(t => new
{
Address = includeAddress ? new AnonymousEventGetAddress
{
AddressLine1 = t.Address.AddressLine1,
CityName = t.Address.AddressCityName
} : new AnonymousEventGetAddress(),
});
I get the following error:
The type 'AnonymousEventGetAddress' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.
What am I doing wrong here (as of with the true
it's working) and how can this be fixed?
I know that changing the else
-part to
new AnonymousEventGetAddress
{
AddressLine1 = null,
CityName = null
}
will work. But if I change the order of the properties then, this will also fail.
The class used is defined the following:
public class AnonymousEventGetAddress : BaseAnonymousObject<AnonymousEventGetAddress>
{
public string AddressLine1 { get; set; }
public string CityName { get; set; }
}
whereas BaseAnonymousObject<AnonymousEventGetAddress>
is defined:
public abstract class BaseAnonymousObject<TAnonymous>
where TAnonymous : BaseAnonymousObject<TAnonymous>
{
// this is used in case I have to return a list instead of a single anonymous object
public static Expression<Func<IEnumerable<TAnonymous>>> Empty => () => new TAnonymous[]{}.AsEnumerable();
}
I don't know why EF has such requirement, but the important thing is that the requirement exists and we need to take it into account.
The first code works because true
is a compile time constant, so the compiler is resolving it at compile time, ending up with one of the two expressions (basically removing the ternary operator). While in the second case it's a variable, thus the expression tree contains the original expression and fails at runtime due to aforementioned EF requirement.
A while ago I was trying to solve this and similar problems (to be honest, mainly for dynamic where
filters) by implementing a custom method which is trying to resolve the bool variables, thus doing at runtime something similar to what does the compiler in the first case. Of course the code is experimental and not tested, but seem to handle properly such scenarios, so you can give it a try. The usage is quite simple:
var eventData = dbContext.Event.Select(t => new
{
Address = includeAddress ? new AnonymousEventGetAddress
{
AddressLine1 = t.Address.AddressLine1,
CityName = t.Address.AddressCityName
} : new AnonymousEventGetAddress(),
}).ReduceConstPredicates();
And here is the helper method used:
public static partial class QueryableExtensions
{
public static IQueryable<T> ReduceConstPredicates<T>(this IQueryable<T> source)
{
var visitor = new ConstPredicateReducer();
var expression = visitor.Visit(source.Expression);
if (expression != source.Expression)
return source.Provider.CreateQuery<T>(expression);
return source;
}
class ConstPredicateReducer : ExpressionVisitor
{
int evaluateConst;
private ConstantExpression TryEvaluateConst(Expression node)
{
evaluateConst++;
try { return Visit(node) as ConstantExpression; }
finally { evaluateConst--; }
}
protected override Expression VisitConditional(ConditionalExpression node)
{
var testConst = TryEvaluateConst(node.Test);
if (testConst != null)
return Visit((bool)testConst.Value ? node.IfTrue : node.IfFalse);
return base.VisitConditional(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
if (node.Type == typeof(bool))
{
var leftConst = TryEvaluateConst(node.Left);
var rightConst = TryEvaluateConst(node.Right);
if (leftConst != null || rightConst != null)
{
if (node.NodeType == ExpressionType.AndAlso)
{
if (leftConst != null) return (bool)leftConst.Value ? (rightConst ?? Visit(node.Right)) : Expression.Constant(false);
return (bool)rightConst.Value ? Visit(node.Left) : Expression.Constant(false);
}
else if (node.NodeType == ExpressionType.OrElse)
{
if (leftConst != null) return !(bool)leftConst.Value ? (rightConst ?? Visit(node.Right)) : Expression.Constant(true);
return !(bool)rightConst.Value ? Visit(node.Left) : Expression.Constant(true);
}
else if (leftConst != null && rightConst != null)
{
var result = Expression.Lambda<Func<bool>>(Expression.MakeBinary(node.NodeType, leftConst, rightConst)).Compile().Invoke();
return Expression.Constant(result);
}
}
}
return base.VisitBinary(node);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (evaluateConst > 0)
{
var objectConst = node.Object != null ? TryEvaluateConst(node.Object) : null;
if (node.Object == null || objectConst != null)
{
var arguments = new object[node.Arguments.Count];
bool canEvaluate = true;
for (int i = 0; i < arguments.Length; i++)
{
var argumentConst = TryEvaluateConst(node.Arguments[i]);
if (canEvaluate = (argumentConst != null))
arguments[i] = argumentConst.Value;
else
break;
}
if (canEvaluate)
{
var result = node.Method.Invoke(objectConst != null ? objectConst.Value : null, arguments);
return Expression.Constant(result, node.Type);
}
}
}
return base.VisitMethodCall(node);
}
protected override Expression VisitUnary(UnaryExpression node)
{
if (evaluateConst > 0 && (node.NodeType == ExpressionType.Convert || node.NodeType == ExpressionType.ConvertChecked))
{
var operandConst = TryEvaluateConst(node.Operand);
if (operandConst != null)
{
var result = Expression.Lambda(node.Update(operandConst)).Compile().DynamicInvoke();
return Expression.Constant(result, node.Type);
}
}
return base.VisitUnary(node);
}
protected override Expression VisitMember(MemberExpression node)
{
object value;
if (evaluateConst > 0 && TryGetValue(node, out value))
return Expression.Constant(value, node.Type);
return base.VisitMember(node);
}
static bool TryGetValue(MemberExpression me, out object value)
{
object source = null;
if (me.Expression != null)
{
if (me.Expression.NodeType == ExpressionType.Constant)
source = ((ConstantExpression)me.Expression).Value;
else if (me.Expression.NodeType != ExpressionType.MemberAccess
|| !TryGetValue((MemberExpression)me.Expression, out source))
{
value = null;
return false;
}
}
if (me.Member is PropertyInfo)
value = ((PropertyInfo)me.Member).GetValue(source);
else
value = ((FieldInfo)me.Member).GetValue(source);
return true;
}
}
}