wake-up-neo.com

Der Typ wird in zwei strukturell inkompatiblen Initialisierungen innerhalb einer einzigen LINQ to Entities-Abfrage angezeigt

Ich versuche, so etwas wie bedingte Abfragen zu erstellen, um nur die benötigten Daten aus der zugrunde liegenden Datenbank zu erhalten.

Derzeit habe ich die folgende Abfrage (die gut funktioniert)

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

Wenn ich es in ändere

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

Ich erhalte folgende Fehlermeldung:

Der Typ 'AnonymousEventGetAddress' wird in zwei strukturell inkompatiblen Initialisierungen innerhalb einer einzigen LINQ-an-Entity-Abfrage angezeigt. Ein Typ kann an zwei Stellen in derselben Abfrage initialisiert werden, jedoch nur, wenn die gleichen Eigenschaften an beiden Stellen festgelegt sind und diese Eigenschaften in derselben Reihenfolge festgelegt sind.

Was mache ich hier falsch (mit der true es funktioniert) und wie kann das behoben werden?

Ich weiß, dass der else- Teil in geändert wird

new AnonymousEventGetAddress
{
    AddressLine1 = null,
    CityName = null
}

wird funktionieren. Wenn ich aber die Reihenfolge der Eigenschaften ändere, wird dies ebenfalls fehlschlagen.

Die verwendete Klasse wird wie folgt definiert:

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

BaseAnonymousObject<AnonymousEventGetAddress> ist definiert:

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

Ich weiß nicht, warum EF diese Anforderung hat, aber das Wichtigste ist, dass die Anforderung existiert und wir müssen sie berücksichtigen.

Der erste Code funktioniert, weil true eine Compile-Zeitkonstante ist. Der Compiler löst ihn also zur Kompilierzeit auf und erhält einen der beiden Ausdrücke (der den ternären Operator grundsätzlich entfernt). Während es sich im zweiten Fall um eine Variable handelt, enthält der Ausdrucksbaum den ursprünglichen Ausdruck und fällt zur Laufzeit aufgrund der oben genannten EF-Anforderung aus. 

Vor einiger Zeit habe ich versucht, dieses und ähnliche Probleme zu lösen (um ehrlich zu sein, hauptsächlich für dynamische where-Filter). Dazu habe ich eine benutzerdefinierte Methode implementiert, die versucht, die Bool-Variablen aufzulösen, und macht zur Laufzeit so etwas wie der Compiler im erster Fall Natürlich ist der Code experimentell und nicht getestet, scheint jedoch mit solchen Szenarien richtig umzugehen, so dass Sie es ausprobieren können. Die Verwendung ist recht einfach:

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

Und hier ist die Hilfsmethode:

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;
        }
    }
}
8
Ivan Stoev

Meiner Meinung nach versuche ich immer, etwas komplizierteres zu verwenden, als Daten in IQueryable auszuwählen, da sie Expression sind, was bedeutet, dass sie niemals ausgeführt werden - sie werden kompiliert.

Also, ich würde dieses Problem so angehen (das hat eine schöne, einfache Atmosphäre):

Erstellen Sie einDTOfür die Rückgabedaten:

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

    public Address Address {get;set;}
}

Dann würde ich deine Logik um die includeAddress aufteilen.

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

Die Methode NoAddress oder wie auch immer Sie sie aufrufen wollen, wird ähnlich aussehen, jedoch ohne Address, und Sie ordnen sie wieder zu.

Sie können dann einfach auswählen:

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

Wenn Sie Select eine Menge Logik haben, würde ich überlegen, es in einen Sproc zu verschieben, wo es einfacher ist, die SQL zu lesen. 

Offensichtlich ist dies nur ein Leitfaden, der Ihnen eine Vorstellung davon gibt, wie Sie das Problem angehen können.

0

In einigen Fällen ist möglicherweise eine einfache Problemumgehung möglich: Lassen Sie den Typ als verschiedene Typen erscheinen. Z.B. machen Sie 2 Unterklassen aus der ursprünglichen Klasse. Diese Problemumgehung ist natürlich ziemlich schmutzig, aber die Anforderung von Linq ist von sich aus künstlich. In meinem Fall hat das geholfen.

0
alehro

Für zukünftige Leser war dieses SO Duplikat (ein Jahr später hinzugefügt) der Schlüssel zur Lösung meiner Probleme:

Der Typ wird in zwei strukturell inkompatiblen Initialisierungen innerhalb einer einzelnen LINQ to Entities-Abfrage angezeigt.

Wenn Sie es sich ansehen, ist die Fehlermeldung sehr deutlich. Bringen Sie die Initialisierungsreihenfolge nicht durcheinander, wenn Sie ein Objekt mehr als einmal im selben Linq-Ausdruck instanziieren. Für mich war das genau das, was ich tat. Bei der Synchronisation der Eigenschaftsinitialisierungen zwischen den beiden Instanziierungsaufrufen strahlte der Compiler erneut die Sonne an.

In diesem Fall:

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

unterscheidet sich von

new AnonymousEventGetAddress()

In der Version 1 der OP-Abfrage kann man mit Sicherheit sagen, dass die abweichende Initialisierung im Zweig else aufgrund der true -Kondition, weshalb sie wahrscheinlich verworfen wurde, für die Version 2, die nicht stattgefunden haben darf, und für uns niemals auftreten konnte Es verbleiben zwei Initialisierungsreihenfolgen, Eigenschaften 1 und 2, und überhaupt keine Eigenschaften. Das sollte es tun:

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

0
nkstr

Ich hatte das gleiche Problem und fand die Lösung, .ToList () vor der select-Funktion hinzuzufügen:

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

Sie können die Bedingungsanweisung in jeden Eigenschafteninitialisierer einfügen.

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