Подтвердить что ты не робот

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

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

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, а не в nullable 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' 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 -Первый. Изменить: добавлены еще два тега.)

4b9b3361

Ответ 1

Я не могу заставить проекцию работать с реализацией IQueryable DbQuery. Если вы ищете обходной путь, то почему бы не сделать проецирование после того, как данные были извлечены из Db, и это уже не E.F. 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();

Недостатком является то, что вы извлекаете все столбцы Order и Customer из Db. Вы можете ограничить это, выбрав только нужные столбцы из Order в анонимный тип, а затем...

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