Frage an .net, entity-framework, linq-to-entities – Kann ich eine optionale Referenz einer Entität in eine optionale Referenz des Ergebnistyps der Projektion projizieren?

13

Angenommen, ich habe zwei Entitäten:

public class Customer
{
    public int Id { get; set; }
    public int SalesLevel { get; set; }
    public string Name { get; set; }
    public string City { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public DateTime DueDate { get; set; }
    public string ShippingRemark { get; set; }

    public int? CustomerId { get; set; }
    public Customer Customer { get; set; }
}

Customer ist einwahlweise (nullable) Referenz inOrder (Vielleicht unterstützt das System "anonyme" Bestellungen).

Nun möchte ich einige Eigenschaften eines Auftrags in ein Ansichtsmodell projizieren, einschließlich einiger Eigenschaften des Kundenob Die Bestellung hat einen Kunden. Ich habe dann zwei Ansichtsmodellklassen:

public class CustomerViewModel
{
    public int SalesLevel { get; set; }
    public string Name { get; set; }
}

public class OrderViewModel
{
    public string ShippingRemark { get; set; }
    public CustomerViewModel CustomerViewModel { get; set; }
}

Wenn dasCustomer wäre einerforderlich Navigationseigenschaft inOrder Ich könnte die folgende Projektion verwenden und es funktioniert, weil ich sicher sein kann, dass aCustomer existiert immer für jedenOrder:

OrderViewModel viewModel = context.Orders
    .Where(o => o.Id == someOrderId)
    .Select(o => new OrderViewModel
    {
        ShippingRemark = o.ShippingRemark,
        CustomerViewModel = new CustomerViewModel
        {
            SalesLevel = o.Customer.SalesLevel,
            Name = o.Customer.Name
        }
    })
    .SingleOrDefault();

Das geht aber nicht wennCustomer ist optional und die Bestellung mit IdsomeOrderId hat keinen Kunden:

EF beschwert sich, dass der materialisierte Wert füro.Customer.SalesLevel istNULL und kann nicht im gespeichert werdenint, nicht nullable EigenschaftCustomerViewModel.SalesLevel. Das ist nicht überraschend und das Problem könnte gelöst werden, indem man machtCustomerViewModel.SalesLevel vom Typint? (oder allgemeinalles Eigenschaften nullable)

Aber das wäre mir eigentlich lieberOrderViewModel.CustomerViewModel materialisiert sich alsnull wenn die Bestellung keinen Kunden hat.

Um dies zu erreichen, habe ich Folgendes versucht:

OrderViewModel viewModel = context.Orders
    .Where(o => o.Id == someOrderId)
    .Select(o => new OrderViewModel
    {
        ShippingRemark = o.ShippingRemark,
        CustomerViewModel = (o.Customer != null)
            ? new CustomerViewModel
              {
                  SalesLevel = o.Customer.SalesLevel,
                  Name = o.Customer.Name
              }
            : null
    })
    .SingleOrDefault();

Dies löst jedoch die berüchtigte Ausnahme von LINQ to Entities aus:

Es kann kein konstanter Wert vom Typ 'CustomerViewModel' erstellt werden. In diesem Zusammenhang werden nur primitive Typen (z. B. "Int32", "String" und "Guid") unterstützt.

Ich vermute, dass: null ist der "konstante Wert" fürCustomerViewModel was nicht erlaubt ist.

Seit der Zuordnungnull scheint nicht erlaubt zu sein Ich habe versucht, eine Marker-Eigenschaft in einzufügenCustomerViewModel:

public class CustomerViewModel
{
    public bool IsNull { get; set; }
    //...
}

Und dann die Projektion:

OrderViewModel viewModel = context.Orders
    .Where(o => o.Id == someOrderId)
    .Select(o => new OrderViewModel
    {
        ShippingRemark = o.ShippingRemark,
        CustomerViewModel = (o.Customer != null)
            ? new CustomerViewModel
              {
                  IsNull = false,
                  SalesLevel = o.Customer.SalesLevel,
                  Name = o.Customer.Name
              }
            : new CustomerViewModel
              {
                  IsNull = true
              }
    })
    .SingleOrDefault();

Dies funktioniert auch nicht und löst die Ausnahme aus:

Der Typ 'CustomerViewModel' wird in zwei strukturell inkompatiblen Initialisierungen innerhalb einer einzelnen LINQ to Entities-Abfrage angezeigt. Ein Typ kann an zwei Stellen in derselben Abfrage initialisiert werden, jedoch nur, wenn an beiden Stellen dieselben Eigenschaften festgelegt wurden und diese Eigenschaften in derselben Reihenfolge festgelegt wurden.

Die Ausnahme ist klar genug, um das Problem zu beheben:

OrderViewModel viewModel = context.Orders
    .Where(o => o.Id == someOrderId)
    .Select(o => new OrderViewModel
    {
        ShippingRemark = o.ShippingRemark,
        CustomerViewModel = (o.Customer != null)
            ? new CustomerViewModel
              {
                  IsNull = false,
                  SalesLevel = o.Customer.SalesLevel,
                  Name = o.Customer.Name
              }
            : new CustomerViewModel
              {
                  IsNull = true,
                  SalesLevel = 0, // Dummy value
                  Name = null
              }
    })
    .SingleOrDefault();

Dies funktioniert, aber es ist keine sehr gute Lösung, alle Eigenschaften mit Dummy-Werten oder zu füllennull ausdrücklich.

Fragen:

Ist das letzte Codefragment die einzige Problemumgehung, abgesehen davon, dass alle Eigenschaften desCustomerViewModel nullable?

Ist es einfach nicht möglich, einen optionalen Verweis auf zu materialisieren?null in einer projektion?

Haben Sie eine alternative Idee, wie Sie mit dieser Situation umgehen sollen?

(Ich stelle nur den General einEntity-Framework Tag für diese Frage, weil ich vermute, dass dieses Verhalten nicht versionsspezifisch ist, aber ich bin nicht sicher. Ich habe die obigen Code-Schnipsel mit EF 4.2 / getestet.DbContext/ Code-First. Bearbeiten: Zwei weitere Tags hinzugefügt.)

Haben Sie jemals eine Lösung dafür gefunden? und wenn ja, würde es Ihnen etwas ausmachen, zu teilen :) Quinton Bernhardt
@QuintonBernhardt: Nein, ich habe keine Lösung gefunden. Ich umgehe dies hauptsächlich, indem ich alle Eigenschaften des verschachtelten Ansichtsmodells auf null setze. Slauma

Deine Antwort

1   die antwort
3

von DbQuery verwenden. Wenn Sie nach einer Problemumgehung suchen, warum nicht die Projektion durchführen, nachdem die Daten aus der Datenbank abgerufen wurden und es keine E.F.-Datenbankabfrage mehr ist?

OrderViewModel viewModel = context.Orders
    .Where(o => o.Id == someOrderId)
     // get from db first - no more DbQuery
    .ToList()
    .Select(o => new OrderViewModel
    {
        ShippingRemark = o.ShippingRemark,
        CustomerViewModel = o.Customer == null ? null : new CustomerViewModel
        {
            SalesLevel = o.Customer.SalesLevel,
            Name = o.Customer.Name
        }
    })
    .SingleOrDefault();

Der Nachteil ist, dass Sie alle Order- und Customer-Spalten aus der Datenbank abrufen. Sie können dies einschränken, indem Sie nur die von Ihnen benötigten Spalten in einem anonymen Typ bestellen und dann ...

OrderViewModel viewModel = context.Orders
    .Where(o => o.Id == someOrderId)
    .Select(o => new { ShippingRemark = o.ShippingRemark, Customer = o.Customer })
     // get from db first - no more DbQuery
    .ToList()
    .Select(o => new OrderViewModel
    {
        ShippingRemark = o.ShippingRemark,
        CustomerViewModel = o.Customer == null ? null : new CustomerViewModel
        {
            SalesLevel = o.Customer.SalesLevel,
            Name = o.Customer.Name
        }
    })
    .SingleOrDefault();
Ich kenne beide Problemumgehungen. Der erste ist schrecklich, um ehrlich zu sein. Ich müsste verwendenInclude für den Kunden zusätzlich zum Laden aller Auftragseigenschaften (oder faulen Laden, das mehrere Abfragen verursachen würde) und dann die meisten geladenen Eigenschaften im Speicher wegwerfen, wenn ich projiziere. Der zweite ist in Ordnung, nur mehr Code zu schreiben, aber nicht wirklich das, wonach ich gesucht habe. Trotzdem +1 für das Beispiel in Teil 2 :) Übrigens: Ich benutze normalerweiseAsEnumerable() anstattToList() Wechseln von LINQ zu Entities zu LINQ zu Objects. Es hat ein bisschen weniger Overhead. Slauma
Danke, gut zu wissen. Quinton Bernhardt

Verwandte Fragen