2012-06-05 14 views
12

dire che ho due entità:Posso proiettare un riferimento facoltativo di un'entità in un riferimento facoltativo del tipo di risultato della proiezione?

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 è un optional (nullable) di riferimento in Order (forse il sistema supporta gli ordini "anonimi").

Ora, desidero proiettare alcune proprietà di un ordine in un modello di vista che include alcune proprietà del cliente se l'ordine ha un cliente. Ho due classi di vista del modello poi:

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

Se il Customer sarebbe una richiesta di proprietà navigazione in Order ho potuto utilizzare il seguente proiezione e funziona perché posso essere sicuro che un Customer esiste sempre per qualsiasi 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(); 

Ma questo non funziona quando Customer è facoltativo e l'ordine con ID someOrderId non avere un cliente:

  • EF lamenta che il valore materializzato per o.Customer.SalesLevel è NULL e non può essere memorizzato nel int, non di proprietà nullable CustomerViewModel.SalesLevel. Questo non è sorprendente e il problema potrebbe essere risolto facendo CustomerViewModel.SalesLevel di tipo int? (o in generale tutti i proprietà nullable)

  • Ma vorrei in realtà preferirei che OrderViewModel.CustomerViewModel si materializza come null quando l'ordine è nessun cliente.

per raggiungere questo obiettivo ho provato quanto segue:

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

Ma questo getta il LINQ famigerato ad eccezione Entità:

Impossibile creare un valore costante di tipo 'CustomerViewModel'. Solo i tipi primitivi (ad esempio '' Int32 ',' String 'e' Guid '') sono supportati in questo contesto .

Immagino che : null sia il "valore costante" per CustomerViewModel che non è consentito.

Dal assegnazione null non sembra essere permesso ho cercato di introdurre una proprietà marcatore in CustomerViewModel:

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

E poi la proiezione:

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

Questo non funziona neanche e getta l'eccezione:

Il tipo "CustomerViewModel" viene visualizzato in due str Inizializzazioni iniziali incompatibili all'interno di una singola query LINQ to Entities. Un tipo può essere inizializzato in due posizioni nella stessa query, ma solo se le stesse proprietà sono impostate in entrambe le posizioni e tali proprietà sono impostate nello stesso ordine .

L'eccezione è abbastanza chiaro come risolvere il problema:

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

Questo funziona, ma non è una bella soluzione per riempire tutte le proprietà con i valori fittizi o null esplicitamente.

Domande:

  1. è l'ultimo frammento di codice l'unica soluzione, oltre a fare tutte le proprietà del CustomerViewModel annullabile?

  2. Semplicemente non è possibile materializzare un riferimento facoltativo a null in una proiezione?

  3. Avete un'idea alternativa su come affrontare questa situazione?

(sto unica impostazione generale entità-quadro tag per questa domanda, perché credo che questo comportamento non è la versione specifica, ma non sono sicuro. Ho testato i frammenti di codice di cui sopra con EF 4.2/DbContext/Code-First Modifica: Altri due tag aggiunti.)

+0

Hai mai trovato una soluzione per questo? e se fosse così ti dispiacerebbe condividere :) –

+0

@QuintonBernhardt: No, non ho trovato una soluzione. Risolvo questo principalmente rendendo annullabili tutte le proprietà del modello di visualizzazione annidate. – Slauma

risposta

3

Non riesco a far funzionare la proiezione sull'implementazione IQueryable di DbQuery. Se siete alla ricerca di una soluzione, allora perché non fare la proiezione dopo che i dati è stato recuperato dal Db e non è più un EF DBQuery ...

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

Il rovescio della medaglia è che stai recupero tutto l'Ordine e le colonne Customer dal Db. È possibile limitare questo selezionando solo le colonne necessarie da ordine in un tipo anonimo e quindi ...

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

Sono a conoscenza di entrambi i workaround. Il primo è terribile, ad essere onesti. Dovrei usare 'Include' per il cliente oltre a caricare tutte le proprietà degli ordini (o il caricamento lazy che causerebbe più query) e poi buttare via la maggior parte delle proprietà caricate quando lavoro. Il secondo è OK, solo più codice da scrivere, ma non proprio quello che stavo cercando.Nondimeno +1 per l'esempio nella parte 2 :) BTW: Io di solito uso 'AsEnumerable()' invece di 'ToList()' per passare da LINQ a Entities a LINQ to Objects. Ha un po 'meno spese generali. – Slauma

+0

Grazie, buono a sapersi. –

Problemi correlati