2012-06-05 14 views
12

Sprich, ich habe zwei Entitäten:Kann ich eine optionale Referenz einer Entität in eine optionale Referenz des Ergebnistyps der Projektion projizieren?

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 eine optional (nullable) Referenz in Order (möglicherweise unterstützt das System "anonymous" Aufträge).

Nun möchte ich einige Eigenschaften eines Auftrags in ein Ansichtsmodell einschließlich einiger Eigenschaften des Kunden projizieren, wenn der Auftrag einen Kunden hat. Ich habe zwei Ansicht Modellklassen dann:

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 die Customer eine erforderlich Navigationseigenschaft in Order würde ich die folgende Projektion verwenden könnte und es funktioniert, weil ich sicher sein kann, dass ein Customer existiert immer für jeden Order :

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

Aber das funktioniert nicht, wenn Customer mit Id someOrderId optional und die Reihenfolge ist noch kein Kunde:

  • EF wirft der materialisierten Wert für o.Customer.SalesLevelNULL ist und nicht in der int gespeichert werden können, nicht auf NULL festlegbare Eigenschaft CustomerViewModel.SalesLevel. Das ist nicht überraschend, und das Problem, indem sie CustomerViewModel.SalesLevel vom Typ gelöst werden kann int? (oder allgemein alle Eigenschaften nullable)

  • Aber ich würde eigentlich lieber, wenn OrderViewModel.CustomerViewModel als null materialisiert wird, wenn der Auftrag keine Kunden hat.

Um dies zu erreichen habe ich versucht, die folgenden:

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

Aber die berüchtigte LINQ to Entities Ausnahme auslöst:

kann nicht einen konstanten Wert des Typs 'CustomerViewModel' erstellen. Nur primitive Typen (zum Beispiel '' Int32 ',' String 'und' Guid '') werden in diesem Zusammenhang unterstützt.

Ich denke, dass : null ist der „konstante Wert“ für CustomerViewModel was nicht erlaubt ist.

Da null Zuordnung scheint nicht zu dürfen, habe ich versucht, einen Marker Eigenschaft in CustomerViewModel vorstellen:

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 wirft die Ausnahme:

Der Typ 'CustomerViewModel' erscheint in zwei str urbanisch inkompatible Initialisierungen innerhalb einer einzelnen LINQ to Entities-Abfrage. Ein Typ kann an zwei Stellen in der gleichen Abfrage initialisiert werden, aber nur wenn dieselben Eigenschaften an beiden Stellen festgelegt sind und diese Eigenschaften in der gleichen Reihenfolge in der festgelegt werden.

Die Ausnahme ist klar genug, wie 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 nicht eine sehr schöne Abhilfe alle Eigenschaften mit Dummy-Werten oder null explizit zu füllen.

Fragen:

  1. ist der letzte Code-Schnipsel, die einzige Lösung, beiseite alle Eigenschaften des CustomerViewModel nullable aus machen?

  2. Ist es einfach nicht möglich, eine optionale Referenz auf null in einer Projektion zu materialisieren?

  3. Haben Sie eine alternative Idee, wie Sie mit dieser Situation umgehen können?

(Ich bin nur den allgemeinen Entity-Framework Tag für diese Frage einstellen, weil ich dieses Verhalten nicht erraten Version spezifisch ist, aber ich bin nicht sicher. Ich den Code-Schnipsel oben mit EF 4.2 getestet/DbContext/Code-First. Bearbeiten: Zwei weitere Tags hinzugefügt.)

+0

Haben Sie jemals eine Lösung dafür gefunden? und wenn ja, würden Sie gerne teilen :) –

+0

@QuintonBernhardt: Nein, ich habe keine Lösung gefunden. Ich umgehe dies hauptsächlich, indem ich alle geschachtelten Ansichtsmodelleigenschaften auf Null setzen kann. – Slauma

Antwort

3

Ich kann nicht die Projektion auf die IQueryable-Implementierung von DbQuery entweder funktionieren. Wenn Sie dieses Problem zu umgehen gesuchte warum dann die Projektion nicht tun, nachdem die Daten aus der DB abgerufen wurde und es ist kein EF dbquery mehr ...

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 Bestellformulare sind holen und Kundenspalten vom Db. Sie können dies einschränken, indem Sie nur die Spalten auswählen, die Sie von der Bestellung in einen anonymen Typ benötigen, 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(); 
+1

Ich kenne beide Problemumgehungen. Der erste ist schrecklich, um ehrlich zu sein. Ich müsste "Include" für den Kunden zusätzlich zum Laden aller Bestelleigenschaften (oder Lazy Loading, was mehrere Abfragen verursachen würde) verwenden und dann die meisten der geladenen Eigenschaften beim Projizieren in den Speicher werfen. Die zweite ist in Ordnung, nur mehr Code zu schreiben, aber nicht wirklich, was ich gesucht habe.Trotzdem +1 für das Beispiel in Teil 2 :) BTW: Ich verwende 'AsEnumerable()' anstelle von 'ToList()', um von LINQ zu Entities zu LINQ zu Objects zu wechseln. Es hat ein bisschen weniger Overhead. – Slauma

+0

Danke, gut zu wissen. –

Verwandte Themen