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

我正在尝试构建像条件查询之类的东西来从底层数据库中获取所需的数据。

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

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

我收到以下错误:

“AnonymousEventGetAddress”类型出现在单个LINQ to Entities查询中的两个结构不兼容的初始化中。 可以在同一查询中的两个位置初始化类型,但前提是在两个位置都设置了相同的属性,并且这些属性以相同的顺序设置。

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

我知道将else -part更改为

 new AnonymousEventGetAddress { AddressLine1 = null, CityName = null } 

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

使用的类定义如下:

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

而定义了BaseAnonymousObject

 public abstract class BaseAnonymousObject where TAnonymous : BaseAnonymousObject { // this is used in case I have to return a list instead of a single anonymous object public static Expression<Func<IEnumerable>> Empty => () => new TAnonymous[]{}.AsEnumerable(); } 

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

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

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

 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 ReduceConstPredicates(this IQueryable source) { var visitor = new ConstPredicateReducer(); var expression = visitor.Visit(source.Expression); if (expression != source.Expression) return source.Provider.CreateQuery(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>(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; } } } 

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

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

在我看来,我总是试图避免把任何更复杂的东西放到IQueryable因为它们是Expression ,这意味着它们永远不会被执行 – 它们被编译。

所以,我会像这样解决这个问题(这有一个很简单的空气):

为返回数据创建DTO

 public class EventDto { // some properties here that you need public Address Address {get;set;} } 

然后我会围绕includeAddress分割你的逻辑

 public IEnumerable 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(); 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确实有很多逻辑,那么我会考虑把它移到sproc中,它会更容易读取SQL。

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

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

我有同样的问题,并找到解决方案,在select-function之前添加.ToList():

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