Скажем, у меня есть два объекта:
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
является необязательной (нулевой) ссылкой в Order
(возможно, система поддерживает "анонимные" заказы).
Теперь я хочу проецировать некоторые свойства заказа в модель представления, включая некоторые свойства клиента , если заказ имеет клиента. У меня есть два класса модели представления:
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; }
}
Если Customer
будет обязательным навигационным свойством в Order
, я мог бы использовать следующий проектор, и он работает, потому что я могу быть уверен, что a Customer
всегда существует для любого 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();
Но это не работает, когда Customer
является необязательным, а порядок с Id someOrderId
не имеет клиента:
-
EF жалуется, что материализованное значение для
o.Customer.SalesLevel
равноNULL
и не может быть сохранено в свойствеint
, а не в nullableCustomerViewModel.SalesLevel
. Это неудивительно, и проблема может быть решена путем созданияCustomerViewModel.SalesLevel
типаint?
(или вообще всех свойств, нулевых) -
Но я бы предпочел, чтобы
OrderViewModel.CustomerViewModel
материализовался какNULL
, когда у заказа нет клиента.
Для этого я попробовал следующее:
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();
Но это порождает печально известное исключение LINQ to Entities:
Невозможно создать постоянное значение типа "CustomerViewModel". Только примитивные типы (например, 'Int32', 'String' und 'Guid' ') являются поддерживается в этом контексте.
Я предполагаю, что : null
является "постоянным значением" для CustomerViewModel
, который не разрешен.
Поскольку присваивание NULL
, похоже, не разрешено, я попытался ввести свойство маркера в CustomerViewModel
:
public class CustomerViewModel
{
public bool IsNull { get; set; }
//...
}
И затем проекция:
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();
Это тоже не работает и генерирует исключение:
Тип "CustomerViewModel" отображается в двух структурно несовместимых инициализации в рамках одного запроса LINQ to Entities. Тип может быть инициализируется в двух местах в одном запросе, но только если тот же свойства задаются в обоих местах, и эти свойства задаются в тот же порядок.
Исключением достаточно ясно, как устранить проблему:
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();
Это работает, но это не очень приятное обходное решение для заполнения всех свойств фиктивными значениями или NULL
явно.
Вопросы:
-
Является ли последний фрагмент кода единственным обходным решением, за исключением того, что все свойства
CustomerViewModel
могут быть нулевыми? -
Невозможно ли материализовать необязательную ссылку на
NULL
в проекции? -
Есть ли у вас альтернативная идея, как справиться с этой ситуацией?
(Я устанавливаю общий тег-инфраструктуру для этого вопроса, потому что я предполагаю, что это поведение не зависит от версии, но я не уверен. Я тестировал фрагменты кода выше с помощью EF 4.2/ DbContext
/Code -Первый. Изменить: добавлены еще два тега.)