KingKerosin
KingKerosin

Reputation: 3841

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();
}

Upvotes: 22

Views: 25527

Answers (6)

nkstr
nkstr

Reputation: 133

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
}

Upvotes: 9

JorgenV
JorgenV

Reputation: 39

I had the same problem, and found the solution to be, to add .ToList() , before the select-function :

var eventData = dbContext.Event.ToList().Select(t => new
{
    Address = includeAddress ? new AnonymousEventGetAddress
    {
        AddressLine1 = t.Address.AddressLine1,
        CityName = t.Address.AddressCityName
    } : new AnonymousEventGetAddress(),
});

Upvotes: -3

alehro
alehro

Reputation: 2208

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.

Upvotes: 1

Ivan Stoev
Ivan Stoev

Reputation: 205729

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;
        }
    }
}

Upvotes: 9

Callum Linington
Callum Linington

Reputation: 14417

In my opinion, I always try to steer away from putting anything more complicated then selecting data into IQueryable because they're Expression, which means they're never executed - they're compiled.

So, I would tackle this problem like so (this has a nice air of simplicity around it):

Create a DTO for the return data:

public class EventDto
{
    // some properties here that you need

    public Address Address {get;set;}
}

Then I would split your logic around the includeAddress

public IEnumerable<EventDto> IncludeAddress(DbContext dbContext)
{
    return dbContext.Event.Select(t => new
    {
        // select out your other properties here

        Address = new
        {
            AddressLine1 = t.Address.AddressLine1,
            CityName = t.Address.AddressCityName
        },
    }).ToList().Select(x => new EventDto { Address = Address });
    // put what ever mapping logic you have up there, whether you use AutoMapper or hand map it doesn't matter.
}

The method NoAddress or whatever you want to call it will look similar but with no Address, and you map it back.

You can then choose which one simply:

var eventDtos = new List<EventDto>();

if (includeAddress)
   eventDtos.AddRange(this.IncludeAddress(dbContext));
else
   eventDtos.AddRange(this.NoAddress(dbContext));

eventDtos.ForEach(e => { if (e.Address == null) e.Address = new Address(); });

If you Select does have a lot of logic in it, then I would consider moving it out into a sproc where it will be easier to read the SQL.

Obviously this is just a guide, gives you an idea on how to tackle the problem.

Upvotes: 0

Maarten
Maarten

Reputation: 22955

You can put the conditional statement in each property initializer.

var eventData = dbContext.Event.Select(t => new
{
    Address = new AnonymousEventGetAddress
    {
        AddressLine1 = includeAddress ? t.Address.AddressLine1 : null,
        CityName = includeAddress ? t.Address.AddressCityName : null
    }
});

Upvotes: 0

Related Questions