El tipo aparece en dos inicializaciones incompatibles estructuralmente dentro de una sola consulta LINQ to Entities

Estoy tratando de construir algo como consultas condicionales para obtener solo los datos necesarios de la base de datos subyacente.

Actualmente tengo la siguiente consulta (que funciona bien)

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

Si lo cambio a

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

Obtuve el siguiente error:

El tipo ‘AnonymousEventGetAddress’ aparece en dos inicializaciones incompatibles estructuralmente dentro de una única consulta LINQ to Entities. Un tipo se puede inicializar en dos lugares en la misma consulta, pero solo si las mismas propiedades se configuran en ambos lugares y esas propiedades se configuran en el mismo orden.

¿Qué estoy haciendo mal aquí (a partir de la true está funcionando) y cómo se puede solucionar esto?

Sé que cambiar la else a

 new AnonymousEventGetAddress { AddressLine1 = null, CityName = null } 

trabajará. Pero si cambio el orden de las propiedades entonces, esto también fallará.

La clase utilizada se define de la siguiente manera:

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

mientras que BaseAnonymousObject está definido:

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

No sé por qué EF tiene ese requisito, pero lo importante es que el requisito existe y debemos tenerlo en cuenta.

El primer código funciona porque true es una constante de tiempo de comstackción , por lo que el comstackdor lo está resolviendo en el momento de comstackr, y termina con una de las dos expresiones (básicamente eliminando el operador ternario). Mientras que en el segundo caso es una variable , el árbol de expresiones contiene la expresión original y falla en el tiempo de ejecución debido al requisito de EF mencionado anteriormente.

Hace un tiempo intentaba resolver este y otros problemas similares (para ser honestos, principalmente para los filtros dynamics where ) implementando un método personalizado que intenta resolver las variables bool, haciendo así algo similar al tiempo de ejecución en el comstackdor en el primer caso Por supuesto, el código es experimental y no está probado, pero parece manejar adecuadamente estos escenarios, por lo que puede intentarlo. El uso es bastante simple:

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

Y aquí está el método de ayuda utilizado:

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

Puede poner la statement condicional en cada inicializador de propiedad.

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

En mi opinión, siempre trato de evitar poner algo más complicado y luego seleccionar datos en IQueryable porque son Expression , lo que significa que nunca se ejecutan, se comstackn.

Por lo tanto, abordaría este problema así (esto tiene un bonito air de simplicidad a su alrededor):

Cree un DTO para los datos de retorno:

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

Entonces dividiría tu lógica en torno a la 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. } 

El método NoAddress o como quiera que lo llame se verá similar pero sin Address , y usted lo asignará nuevamente.

A continuación, puede elegir cuál simplemente:

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

Si Select tiene mucha lógica, entonces consideraría moverlo a un espacio donde sea más fácil leer el SQL.

Obviamente, esto es solo una guía, le da una idea de cómo abordar el problema.

En algunas circunstancias, podría ser posible una solución simple: hacer que el tipo aparezca como tipos diferentes. Por ejemplo, hacer 2 subclases de la clase original. Esta solución es, por supuesto, bastante sucia, pero el requisito de Linq es artificial por sí mismo. En mi caso esto ayudó.

Tuve el mismo problema y encontré la solución para agregar .ToList () antes de la función de selección:

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