Могу ли я проецировать необязательную ссылку на сущность в необязательную ссылку типа результата проекции?

Скажем, у меня есть две сущности:

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.

Вопросы:

  1. Является ли последний фрагмент кода единственным обходным решением, помимо того, что все свойства CustomerViewModelмогут принимать значение NULL?

  2. Просто невозможно материализовать необязательную ссылку на nullв проекции?

  3. Есть ли у вас альтернативные идеи, как поступить в этой ситуации?

(Я устанавливаю только общий тег entity-frameworkдля этого вопроса, потому что я предполагаю, что это поведение не зависит от версии, но я не уверен. Я протестировал приведенные выше фрагменты кода с EF 4.2. / DbContext/Code-First. Редактировать: добавлено еще два тега.)

13
задан Slauma 7 June 2012 в 16:00
поделиться