Получить тип значения по умолчанию (DateTime) в запросе Linq to Sql с пустыми результатами - PullRequest
1 голос
/ 27 июня 2011

У меня проблема с возвратом значения по умолчанию DateTime из сложного Linq-to-Sql запроса.

Надеюсь, следующий упрощенный пример показывает проблему (хотя я не запускал этот точный код):

users.Select(u =>
  new MyDomainObject(
     u.Id,
     u.Transactions
        .Where(t => false) // empty results set
        .Select(t => t.TransactionTime) // TransactionTime is DATETIME NOT NULL
        .OrderByDescending(x => x)
        .FirstOrDefault() // I want DateTime.MinValue (or SqlDateTime.MinValue)
  )
); 

Поэтому мне нужна последняя отметка времени или некоторая отметка времени MinValue, если нет результатов.

При перечислении вышеупомянутого запроса выдается ошибка

The null value cannot be assigned to a member with type System.DateTime

ОБНОВЛЕНИЕ

Хорошо, я не уверен, что моего приведенного выше примера было достаточно для иллюстрации проблемы.Я полагаю, что ошибка может быть связана с тем, как я пытаюсь выполнить подзапрос для третьей связанной таблицы.

В следующем примере воссоздается точная ошибка:

enter image description here

Итак, у меня есть машина, которую я могу отвезти к механику, которая иногда (но не всегда) обслуживается механиком.

Требуется найти через запрос ктаблица машин сколько посещений механика было за каждую машину с момента последнего обслуживания.Проблема в том, что автомобиль никогда не обслуживался, поэтому данные выглядят следующим образом:

Car
------------- 
Id: 1         


MechanicVisit          
------------- 
Id: 1 
CarId: 1 
ServiceRecordId: NULL
VisitDate: 1 Jan 2011    


ServiceRecord
------------- 
<empty>

Итак, простой пример, показывающий ошибку, - это запрос для получения списка времени последнего обслуживания:

 var test = _dataContext.GetTable<Car>
                 .Select(c => 
                     c.MechanicVisits
                      .Select(m => m.ServiceRecord)
                      .Select(s => s.ServiceDate)
                      .OrderByDescending(d => d)
                      .FirstOrDefault()
                  ).ToList();

Это дает ранее описанную ошибку при попытке присвоить значение null ненулевому типу, где мне нужно вернуть DateTime.MinValue или SqlDateTime.MinValue, когда дата равна нулю (так что я могу сделать фактическуюзапрос, который является числом посещений механика с момента последнего обслуживания)


SOLUTION

Я использовал вариант, предложенный Джоном Скитом, используя приведение к DateTime? и оператор объединения нулей:

 var test = _dataContext.GetTable<Car>
                     .Select(c => 
                         c.MechanicVisits
                          .Select(m => m.ServiceRecord)
                          .Select(s => (DateTime?)s.ServiceDate)
                          .OrderByDescending(d => d)
                          .FirstOrDefault() ?? new DateTime(1900, 1, 1)
                      ).ToList();

Обратите внимание на использование конструктора paramatered для даты "по умолчанию" - DateTime.MinValue здесь не может использоваться, так как он выдает исключение вне диапазона при преобразовании в SQLи SqlDateTime.MinValue нельзя использовать, так как он не обнуляется (поэтому оператор coalesce становится недействительным).

Я до сих пор не совсем понимаю, почему произошла первоначальная ошибка, и это решение чувствуетнемного хак, буt Я не смог найти более аккуратный способ исправить это.

Ответы [ 4 ]

3 голосов
/ 27 июня 2011

Я бы очень хотел на самом деле использовать для этого обнуляемый DateTime.Например, из вашего образца "Car":

var test = _dataContext.GetTable<Car>
                       .Select(c => 
                           c.MechanicVisits
                            .Select(m => m.ServiceRecord)
                            .Select(s => (DateTime?) s.ServiceDate)
                            .OrderByDescending(d => d)
                            .FirstOrDefault()
                       ).ToList();

Таким образом, я подозреваю, что в итоге вы получите , работающий и предоставляющий вам нулевые DateTime? значения.Вы всегда можете преобразовать это позже, если хотите:

var test = _dataContext.GetTable<Car>
                       .Select(c => 
                           c.MechanicVisits
                            .Select(m => m.ServiceRecord)
                            .Select(s => (DateTime?) s.ServiceDate)
                            .OrderByDescending(d => d)
                            .FirstOrDefault()
                       ).AsEnumerable()
                        .Select(dt => dt ?? DateTime.MinValue)
                        .ToList();

Оригинальный ответ (не работает)

Хмм.Я не буду утверждать, что полностью понимаю причины этого, но вот потенциальный обходной путь:

users.Select(u =>
  new MyDomainObject(
     u.Id,
     u.Transactions
        .Where(t => false)
        .Select(t => t.TransactionTime)
        .OrderByDescending(x => x)
        .DefaultIfEmpty(DateTime.MinValue)
        .First()
  )
); 

Другими словами, если набор результатов пуст, используйте указанный по умолчанию - и затем возьмите первый результатпоследовательности «теперь определенно не пустой».

1 голос
/ 27 июня 2011
users.Select(u =>
  new MyDomainObject(
     u.Id,
     u.Transactions
        .Where(t => false) // empty results set
        .Select(t => t.TransactionTime)
        .Any() ?
     u.Transactions
        .Where(t => false) // empty results set
        .Select(t => t.TransactionTime) // TransactionTime is DATETIME NOT NULL
        .OrderByDescending(x => x)
        .FirstOrDefault() : // I want DateTime.MinValue (or SqlDateTime.MinValue)
     DateTime.MinValue
  )
);

Это предоставит DateTime.MinValue, если для этого объекта нет доступных транзакций.

1 голос
/ 27 июня 2011

Ошибка подразумевает, что t.TransactionTime имеет значение null (т.е. DateTime?). Даже если поле не может быть пустым в базе данных, если свойство имеет значение null

EDIT

в дополнение к вашим комментариям, это очень странно: приведенный ниже код выводит True

List<DateTime> dates = new List<DateTime>();
DateTime date = dates.FirstOrDefault();
Console.WriteLine(date == DateTime.MinValue);

И я ожидаю, что ваш код будет делать то же самое.

0 голосов
/ 27 июня 2011

Это предоставит DateTime.MinValue, если для этого объекта нет доступных транзакций. users.Select (u => новый MyDomainObject ( u.Id, u.Transactions .Where (t => false) // пустой набор результатов .Select (t => t.TransactionTime) .Любой() ? u.Transactions .Where (t => false) // пустой набор результатов .Select (t => t.TransactionTime) // TransactionTime имеет значение DATETIME, а не NULL .OrderByDescending (x => x) .FirstOrDefault (): // я хочу DateTime.MinValue (или SqlDateTime.MinValue) DateTime.MinValue ) );

...