该类型出现在单个 LINQ to Entities 查询中的两个结构上不兼容的初始化中

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

我正在尝试构建诸如条件查询之类的东西,以便从基础数据库中仅获取所需的数据。

目前我有以下查询(工作正常)

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

如果我改成

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

我收到以下错误:

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.

我在这里做错了什么(因为 true 它正在工作),如何解决这个问题?

我知道将 else 部分更改为

new AnonymousEventGetAddress
{
    AddressLine1 = null,
    CityName = null
}

会起作用。但是如果我改变属性的顺序,这也会失败。

使用的class定义如下:

public class AnonymousEventGetAddress : BaseAnonymousObject<AnonymousEventGetAddress>
{
    public string AddressLine1 { get; set; }
    public string CityName { get; set; }
}

BaseAnonymousObject<AnonymousEventGetAddress> 定义为:

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

您可以将条件语句放在每个 属性 初始值设定项中。

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

我不知道为什么EF有这样的要求,但重要的是这个要求存在,我们需要考虑到它。

第一个代码有效,因为 true 是一个 编译时间常量 ,所以编译器在编译时解析它,以两个表达式之一结束 (基本上删除了三元运算符)。而在第二种情况下,它是一个 变量 ,因此表达式树包含原始表达式,并且由于上述 EF 要求而在运行时失败。

前一段时间我试图通过实现一个试图解析 bool 变量的自定义方法来解决这个问题和类似的问题(老实说,主要是针对动态 where 过滤器),因此在运行时进行类似于第一种情况下编译器所做的事情。当然代码是实验性的,没有经过测试,但似乎可以正确处理这种情况,所以你可以试一试。用法很简单:

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

这里是使用的辅助方法:

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

在我看来,我总是尽量避免将任何更复杂的东西放在 IQueryable 中,因为它们是 Expression,这意味着它们永远不会被执行 - 它们是已编译。

所以,我会这样解决这个问题(这有一种很好的简单性):

为 return 数据创建一个 DTO

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

    public Address Address {get;set;}
}

然后我会围绕 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.
}

方法 NoAddress 或任何您想调用的方法看起来很相似,但没有 Address,然后您将其映射回来。

然后您可以简单地选择哪一个:

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

如果您 Select 中确实有很多逻辑,那么我会考虑将其移出到存储过程中,以便更容易阅读 SQL。

显然这只是一个指南,让您了解如何解决问题。

在某些情况下,可能会有简单的解决方法:使类型显示为不同的类型。例如。从原来的 class 制作 2 个子 classes。这种解决方法当然很脏,但 Linq 要求本身就是人为的。就我而言,这有所帮助。

我遇到了同样的问题,发现解决方案是在 select-函数之前添加 .ToList() :

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

对于未来的读者,这个 SO 副本(一年后添加)是解决我的问题的关键:

当您查看它时,错误消息非常清楚。如果您在同一个 Linq 表达式中多次实例化一个对象,请不要弄乱初始化顺序。对我来说,这正是我正在做的。在两个实例化调用之间同步 属性 初始化让编译器再次吹阳光。

在这种情况下:

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

不同于

new AnonymousEventGetAddress()

在 OP 查询版本 1 中,可以肯定地说,由于 true 条件,else 分支中的异常初始化永远不会发生,为什么它可能被丢弃,对于不能有的版本二发生了,我们剩下两个初始化顺序,属性 1 和 2 与根本没有属性。 应该这样做:

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