Kann ich eine optionale Referenz einer Entität in eine optionale Referenz des Ergebnistyps der Projektion projizieren?

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

Antworten auf die Frage(1)

Ihre Antwort auf die Frage