【问题标题】:LINQ - Match value, if not, return NULL elementLINQ - 匹配值,如果不匹配,则返回 NULL 元素
【发布时间】:2019-11-07 14:04:07
【问题描述】:

我有一个给定的日期,我想在表格上获取大于给定日期的元素,但如果找不到,我想返回带有NULL 日期的元素。

例如:

表格:

如果给定的日期是例如 2019-10-20,我想返回 ID 为 3 的元素。

如果给定的日期是例如 2019-11-20,我想返回 ID 为 4 的元素。

我试过这样做:

var parameter = _db.Parameter
.OrderBy(x => x.EndDate)
.First(givenDate < x.EndDate) || x.EndDate == null);

但它总是返回最后一个元素。我需要做什么?

【问题讨论】:

  • OrderBy 会将空值放在首位,因此您的 First 方法将始终返回空值。
  • @Taian 答案已更新以优化速度和内存。

标签: c# linq linq-to-sql


【解决方案1】:

你可以试试这个:

var query = _db.Parameter
            .OrderBy(x => x.EndDate)
            .ToList();

var parameter = query.FirstOrDefault(x => givenDate < x.EndDate);

if ( parameter == null )
  parameter = query.FirstOrDefault(x => x.EndDate == null);

if ( parameter == null )
  ...
else
  ...

我们在日期上创建一个初始有序查询,获取一个列表来评估查询。

接下来我们检查第一个 givenDate 是否匹配期望的结果。

否则我们尝试获取第一个空行。

然后你就可以管理最后的案例了。

为避免解析两次查询,您可以使用:

TheTypeOfParameter parameterFound = null;
TheTypeOfParameter parameterHavingDateNotNull = null;
TheTypeOfParameter parameterHavingDateNull = null;

bool foundDateNull = false;
bool foundDateNotNull = false;

foreach ( var item in query )
{
  if ( !foundDateNull && item.EndDate == null )
  {
    parameterHavingDateNull = item;
    foundDateNull = true;
  }
  else
  if ( !foundDateNotNull && item.EndDate > givenDate )
    foundDateNotNull = true;
  if ( !foundDateNotNull )
    parameterHavingDateNotNull = item;
  if ( foundDateNotNull || foundDateNull )
    break;
}

parameterFound = parameterHavingDateNotNull != null
               ? parameterHavingDateNotNull
               : parameterHavingDateNull;

因为我无法测试和调试,我希望这个循环能够工作......

【讨论】:

  • 这至少会迭代集合两次。
  • 确实如此。你知道如何在不使用 Linq 而不是循环的情况下这样做吗?
【解决方案2】:

我会通过不为此使用 LINQ 来优化代码:

var parameter = _db.Parameter[0]; // you may need to handle that there's at least 1 item.
for (int i = 1; i < _db.Parameter.Count; i++)
{
    var param = _db.Parameter[i];
    if (param.EndDate > givenDate)
    { // param is good
        if (parameter.EndDate == null || parameter.EndDate > param.EndDate)
            parameter = param; // replace parameter with param
    }
    else if (parameter.EndDate != null && parameter.EndDate < givenDate)
    { // parameter precedes given date, replace it!
        parameter = param;
    }
}

与目前提供的其他解决方案不同,这只会遍历您的列表一次。

如果您必须使用 LINQ 并且想要迭代一次,也许您可​​以使用下面的代码,但它会返回一个动态变量,因此您需要将其转换回 Parameter。它的工作原理是将NULL 替换为DateTime.MaxValue,这样当您执行OrderBy 时,NULL 的条目将排在底部。

var param = _db.Parameter
    .Select(x => new
    {
        ID = x.ID,
        EndDate = (x.EndDate.HasValue) ? x.EndDate : DateTime.MaxValue,
        Value = x.Value
    })
    .OrderBy(x => x.EndDate)
    .FirstOrDefault();

var parameter = new Parameter()
    {
        ID = param.ID,
        EndDate = (param.EndDate == DateTime.MaxValue) ? null : param.EndDate,
        Value = param.Value
    };

【讨论】:

    【解决方案3】:

    正如 cmets 中提到的,NULL 将在排序之后。 如果您尝试以下操作会怎样:

    var parameter = _db.Parameter
        .Where(x => (x.EndDate > givenDate) || (x.EndDate == null))
        .OrderBy(x => x.EndDate)
        .Last();
    

    仅选择较早的日期后,选择最新的日期。如果列表中只有一个元素(NULL 元素),则选择该元素。

    【讨论】:

    • 抱歉,我已经更正了这个问题。所以,这个建议没有奏效。它不返回任何元素。
    • 我刚刚检查了(在将“位置”条件对齐后)它与您提供的数据 - 它返回了您要求的内容。再次检查代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-08
    相关资源
    最近更新 更多