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

Kin*_*sin 16 c# linq-to-entities entity-framework anonymous-types

我正在尝试构建类似条件查询的东西,以便从底层数据库中获取所需的数据.

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

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

如果我改成它

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(),
    });
Run Code Online (Sandbox Code Playgroud)

我收到以下错误:

"AnonymousEventGetAddress"类型出现在单个LINQ to Entities查询中的两个结构不兼容的初始化中.A型可以在同一个查询两个地方被初始化,但只有当同一属性在两个地方设置和这些属性以相同的顺序设置.

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

我知道将else-part改为

new AnonymousEventGetAddress
{
    AddressLine1 = null,
    CityName = null
}
Run Code Online (Sandbox Code Playgroud)

将工作.但是,如果我改变属性的顺序,那么这也将失败.

使用的类定义如下:

public class AnonymousEventGetAddress : BaseAnonymousObject<AnonymousEventGetAddress>
{
    public string AddressLine1 { get; set; }
    public string CityName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

小智 9

对于未来的读者来说,这个重复的内容(一年后添加)是解决我的问题的关键:

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

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

在这种情况下:

new AnonymousEventGetAddress
{
    AddressLine1 = t.Address.AddressLine1,
    CityName = t.Address.AddressCityName
} 
Run Code Online (Sandbox Code Playgroud)

不同于

new AnonymousEventGetAddress()
Run Code Online (Sandbox Code Playgroud)

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

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

Run Code Online (Sandbox Code Playgroud)


Iva*_*oev 8

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

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

前一段时间我试图where通过实现一个试图解析bool变量的自定义方法来解决这个和类似问题(说实话,主要是动态过滤器),从而在运行时做类似于第一个编译器的东西案件.当然代码是实验性的而没有经过测试,但似乎可以正确处理这些场景,所以你可以尝试一下.用法很简单:

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

这是使用的辅助方法:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)