Скажем, у меня есть две сущности:
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
будет requiredнавигационным свойством в 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();
Но это не работает, когда Клиент
является необязательным и заказ с идентификатором ] someOrderId
не имеет клиента:
EF жалуется, что материализованное значение для o.Customer.SalesLevel
равно NULL
и не может быть сохранено в int
, свойство, не допускающее значение NULL CustomerViewModel.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» и «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?
Просто невозможно материализовать необязательную ссылку на null
в проекции?
Есть ли у вас альтернативные идеи, как поступить в этой ситуации?
(Я устанавливаю только общий тег entity-frameworkдля этого вопроса, потому что я предполагаю, что это поведение не зависит от версии, но я не уверен. Я протестировал приведенные выше фрагменты кода с EF 4.2. / DbContext
/Code-First. Редактировать: добавлено еще два тега.)