在Linq to Sql查询中获取具有空结果的默认值类型(DateTime)

时间:2011-06-27 16:06:44

标签: c# linq-to-sql default-value value-type

我在从复杂的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.MinValueSqlDateTime.MinValue(所以我可以做实际查询,即自上次服务以来的机械访问次数)


我使用了Jon Skeet建议的变体,使用强制转换为DateTime?和null合并运算符:

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

注意,对于“默认”日期使用了参数化的构造函数 - DateTime.MinValue不能在这里使用,因为它在转换为SQL时会抛出超出范围的异常,并且SqlDateTime.MinValue不能因为它不可为空(因此合并运算符变为无效)。

我仍然不明白为什么原始错误发生了,这个解决方案确实感觉有点hacky,但我一直无法找到任何更简洁的修复方法。

4 个答案:

答案 0 :(得分:3)

我很想实际上使用可以为空的DateTime。例如,从您的“汽车”样本:

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

换句话说,如果结果集为空,则使用指定的默认值 - 然后获取now-definitely-not-empty序列的第一个结果。

答案 1 :(得分:1)

错误意味着t.TransactionTime可以为空(即DateTime?)。即使该字段在数据库中不能为null,如果该属性可以为空并且查询没有返回任何行,则FirstOrDefault()将返回null,因为这是t的默认值.TransactionTime

修改

进一步对你的评论,这很奇怪:下面的代码输出True

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

我希望你的代码也能这样做。

答案 2 :(得分:1)

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

答案 3 :(得分:0)

如果没有可用于该对象的交易,这将提供DateTime.MinValue。     users.Select(u =&gt;       新的MyDomainObject(          u.Id,          u.Transactions             .Where(t =&gt; false)//空结果集             。选择(t =&gt; t.TransactionTime)             。任何() ?          u.Transactions             .Where(t =&gt; false)//空结果集             .Select(t =&gt; t.TransactionTime)// TransactionTime是DATETIME NOT NULL             .OrderByDescending(x =&gt; x)             .FirstOrDefault()://我想要DateTime.MinValue(或SqlDateTime.MinValue)          DateTime.MinValue       )     );