简体   繁体   中英

Selecting changes in values with Lambda, Linq, Entity Framework

I have the following Entity Framework class definition:

class TimeValue
{
    DateTime StartDate;
    double Value;
}

Say given the following series of values I only want to know when the Value changes:

2000-01-01  100
2000-01-15  100
2000-02-01  110
2000-02-15  120
2000-03-01  120
2000-03-15  50
2000-04-01  50
2000-04-15  50
2000-05-01  120

So the result would be:

2000-01-01  100
2000-02-01  110
2000-02-15  120
2000-03-15  50
2000-05-01  120

I can select the values fine using lambda/linq. Then I iterate through the results using the following code to add to a list:

var timeValueQuery = _context.TimeValues.Where(...);

List<TimeValue> timeChanges = new List<TimeValue>();
TimeValue lastValue = null;
foreach (var tvq in timeValueQuery)
{
    if (lastValue == null || tvq.Value != lastValue.Value)
    {
        timeChanges.Add(tvq);
    }
    lastValue = tvq;
}

Just wondered if there was a quicker/nicer way to do it.

You can create extension method which will return item only if some condition is met. This condition (predicate) will accepts two parameters - previous and current item, which it should compare:

public static IEnumerable<T> TakeIf<T>(this IEnumerable<T> source, 
                                       Func<T, T, bool> predicate)
{
    var enumerator = source.GetEnumerator();
    if (!enumerator.MoveNext())
        yield break;

    yield return enumerator.Current;
    T previous = enumerator.Current;

    while (enumerator.MoveNext())
    {
        T current = enumerator.Current;
        if (predicate(previous, current))
            yield return current;

        previous = current;
    }
}

Pass predicate which checks if previous and current items have different values. Usage:

var timeChanges = _context.TimeValues.TakeIf((x, y) => x.Value != y.Value);

Edited after reading your dataset again.

I see a grouping wouldn't work. Ideally though, you want this logic on the db server, not client side due to network io, etc.

You can either write this as a proc or dynamic sql. Dynamic sql maybe easier in the near term.

Simple method, select it into a cursor. Loop into a temp table variable and return the result set.

If you dataset is really small, then stick with the easy to read linq loop you have. Don't add more complexity unless you need too. ie don't micro-optimize.

Don't know if this is nicer :) anyway:

  IQueryable<TimeValue> values = ...; // initialize here
  var query = from v in values
              where (from v2 in values
                     orderby v2.StartDate
                     where v2.StartDate > v.StartDate
                     select v2.Value).FirstOrDefault() != v.Value
              select v;

If zero value is possible, change FirstOrDefault() to FirstOrDefault(impossible_value). Also for cycle may be more efficient (but you asked for LINQ).

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