【问题标题】:Get default value type (DateTime) in Linq to Sql query with empty results在 Linq to Sql 查询中获取默认值类型(DateTime),结果为空
【发布时间】:2011-09-23 15:40:06
【问题描述】:

我在从复杂的 Linq-to-Sql 查询返回默认 DateTime 值时遇到问题。

希望下面的简化示例能说明问题(虽然我没有运行这个确切的代码):

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 分配给非可空类型的错误,我需要做的是在日期为 null 时返回 DateTime.MinValueSqlDateTime.MinValue(这样我就可以执行实际查询是自上次服务以来的机械师访问次数)


解决方案

我使用了 Jon Skeet 建议的变体,使用了 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();

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

我仍然不明白为什么会发生最初的错误,而且这个解决方案确实有点老套,但我找不到任何更巧妙的方法来修复它。

【问题讨论】:

  • 哪个成员属于 DateTime 类型?目前还不清楚发生了什么......
  • t.TrnsactionTime 是数据库中的 datetime not null 类型,MyDomainObject 的第二个参数应该是 DateTime。哪一点不清楚?我会尽力解释得更好。
  • 好的,这更清楚了。只是为了检查一下,t.TransactionTime 在您的 LINQ to SQL 模型中也不能为空吗?
  • UsersTransactions是什么关系?
  • @Jon 是的,在 DB 和 Linq 实体中都不可为空

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


【解决方案1】:

我很想实际上为此使用可以为空的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 序列的第一个结果。

【讨论】:

  • 这看起来像我需要的,但它正在抛出System.NotSupportedException: Unsupported overload used for query operator 'DefaultIfEmpty'。现在我更困惑了!
  • @fearofawhack:Drat。好吧,这没什么用……再想想吧。
  • @Jon 好的,我想肯定有一些我没有发现的事情发生。我正在尝试做一个端到端的示例,当我有一些可以更好地证明问题的东西时,我会更新问题。如果您能稍后或明天某个时候再检查一下这个问题,我将不胜感激。
  • @fearofawhackplanet:一个完整​​的例子会很棒,谢谢。我建议您在完成后对此答案添加评论,否则我可能会忘记检查:)
  • @Jon: 好的,我添加了一个更新,它完全重新创建了错误。如果您再次找到此线程但仍未解决,将不胜感激。我敢肯定它一定是显而易见的,但我无法发现它!谢谢
【解决方案2】:
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】:

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

    编辑

    进一步说你的 cmets,这很奇怪:下面的代码输出 True

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

    我希望你的代码也能做到这一点。

    【讨论】:

    • 不,它是数据库中不可为空的日期时间字段,它被 Linq ORM 正确映射到一个普通的旧日期时间属性。
    【解决方案4】:

    如果该对象没有可用的事务,这将提供DateTime.MinValue,例如:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多