我在从复杂的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
更新
好的我不确定我上面的例子是否足以说明问题。我相信这个错误可能与我试图在第三个链表上做一个子查询有关。
以下示例重新创建了确切的错误:
所以我有一辆车,我可以带到机修工,有时(但不总是)由机修工服务。
要求是通过查询车牌表 每辆车自上次维修以来有多少机械师访问。问题是从未对汽车进行维修,因此数据如下:
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
(所以我可以做实际查询,即自上次服务以来的机械访问次数)
解
我使用了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,但我一直无法找到任何更简洁的修复方法。
答案 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
)
);