Posso projetar uma referência opcional de uma entidade em uma referência opcional do tipo de resultado da projeção?
Diga, eu tenho duas entidades:
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
é umopcional (anulável) referência emOrder
(talvez o sistema suporte pedidos "anônimos").
Agora, quero projetar algumas propriedades de um pedido em um modelo de exibição, incluindo algumas propriedades do clienteE se o pedido tem um cliente. Eu tenho duas classes de modelos de visão, em seguida:
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 oCustomer
seria umrequeridos propriedade de navegação emOrder
Eu poderia usar a seguinte projeção e funciona porque eu posso ter certeza que umCustomer
sempre existe para qualquerOrder
:
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();
Mas isso não funciona quandoCustomer
é opcional e o pedido com o IDsomeOrderId
não tem cliente:
A EF reclama que o valor materializado parao.Customer.SalesLevel
éNULL
e não pode ser armazenado noint
Propriedade não anulávelCustomerViewModel.SalesLevel
. Isso não é surpreendente e o problema poderia ser resolvido fazendoCustomerViewModel.SalesLevel
do tipoint?
(ou geralmentetodos propriedades anuláveis)
Mas eu realmente preferiria issoOrderViewModel.CustomerViewModel
é materializado comonull
quando o pedido não tem cliente.
Para conseguir isso, tentei o seguinte:
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();
Mas isso lança a infame exceção LINQ to Entities:
Não é possível criar um valor constante do tipo 'CustomerViewModel'. Apenas tipos primitivos (por exemplo, 'Int32', 'String' e 'Guid' ') são suportados neste contexto.
eu acho que: null
é o "valor constante" paraCustomerViewModel
o que não é permitido.
Desde a atribuiçãonull
não parece ser permitido eu tentei introduzir uma propriedade de marcador emCustomerViewModel
:
public class CustomerViewModel
{
public bool IsNull { get; set; }
//...
}
E então a projeção:
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();
Isso também não funciona e lança a exceção:
O tipo 'CustomerViewModel' aparece em duas inicializações incompatíveis estruturalmente dentro de uma única consulta LINQ to Entities. Um tipo pode ser inicializado em dois locais na mesma consulta, mas apenas se as mesmas propriedades estiverem definidas nos dois locais e essas propriedades forem definidas na mesma ordem.
A exceção é clara o suficiente para corrigir o 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();
Isso funciona, mas não é uma solução muito boa para preencher todas as propriedades com valores fictícios ounull
explicitamente.
Questões:
É o último código trecho a única solução, além de fazer todas as propriedades doCustomerViewModel
anulável?
Simplesmente não é possível materializar uma referência opcional paranull
em uma projeção?
Você tem uma ideia alternativa de como lidar com essa situação?
(Eu estou apenas definindo o geralEstrutura de entidade tag para esta pergunta porque eu acho que esse comportamento não é específico da versão, mas não tenho certeza. Eu testei os trechos de código acima com EF 4.2 /DbContext
/ Código-primeiro. Editar: mais duas tags adicionadas.