Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The type appears in two structurally incompatible initializations within a single LINQ to Entities query

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();
}
like image 443
KingKerosin Avatar asked Aug 25 '16 07:08

KingKerosin


3 Answers

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;
        }
    }
}
like image 86
Ivan Stoev Avatar answered Oct 20 '22 21:10

Ivan Stoev


For future readers, this SO duplicate (added one year later) was key to solving my problems:

The type appear in two structurally incompatible initializations within a single LINQ to Entities query

When you look at it, the error message is abundantly clear. Don't mess up the initialization order if you are instantiating an object more than once in the same Linq expression. For me, that was exactly what I was doing. Synchronizing the property initializations between the two instantiation calls had the compiler blowing sunshine again.

In this case:

new AnonymousEventGetAddress
{
    AddressLine1 = t.Address.AddressLine1,
    CityName = t.Address.AddressCityName
} 

is different from

new AnonymousEventGetAddress()

In OP query version 1 it is safe to say that the aberrant initialization in the else branch could never happen due to the the true conditional, why it was probably discarded, for version two that must not have happened, and we're left with two initialization orders, properties 1 and 2 versus no properties at all. This should do it:

includeAddress
? new AnonymousEventGetAddress
{
    AddressLine1 = t.Address.AddressLine1,
    CityName = t.Address.AddressCityName
}
: new AnonymousEventGetAddress
{
    AddressLine1 = null,
    CityName = null
}

like image 34
nkstr Avatar answered Oct 20 '22 20:10

nkstr


In some circumstances there simple workaround might be possible: make the type appear as different types. E.g. make 2 subclasses from the original class. This workaround is of course quite dirty but the Linq requirement is artificial by itself. In my case this helped.

like image 37
alehro Avatar answered Oct 20 '22 19:10

alehro