简体   繁体   中英

LINQ - Match value, if not, return NULL element

I have a given date and I want to get the element on a table that is greater than the given date, but if it's not found, I want to return the element with the NULL date.

For example:

Table:

在此处输入图像描述

If the given date is, for example, 2019-10-20, I want to return the element with the ID 3.

If the given date is, for example, 2019-11-20, I want to return the element with the ID 4.

I've tried doing this:

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

But it's always returning the last element. What do I need to do?

You can try this:

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
  ...

We create an initial ordered query on the date, taking a list to evaluate the query.

Next we check if the first givenDate matching the desired result.

Else we try to get the first null row.

Then you can manage the final case.

To avoid parsing twice the query you can use that:

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;

Because I can't test and debug, I hope this loop will work...

I would optimise the code by NOT using LINQ for this:

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

This will iterate through your list just once, unlike the other solutions provided so far.

If you MUST use LINQ and want to iterate once, maybe you can use the below, which will return a dynamic though, so you need to convert it back to a Parameter . It works by replacing the NULL with DateTime.MaxValue so that when you do an OrderBy , the entries that were NULL would be ordered at the bottom.

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

As mentioned in the comments, NULL will be first after the ordering. What if you try the following:

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

After selecting only earlier dates the latest is chosen. If only one element is in the list (the NULL element), this one gets chosen.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM