高效的LINQ to Entities查询

时间:2013-05-27 06:02:50

标签: c# .net linq entity-framework linq-to-entities

我有一个Readings的实体集合。 每个Reading都链接到名为Meter的实体。 (每个Meter都有多个读数)。 每个Reading都包含一个米的id(int)字段和一个时间字段。

以下是一些演示它的简化代码:

public class Reading
{
    int Id;
    int meterId;
    DateTime time;
}

public class Meter
{
    int id;
    ICollection<Readings> readings;    
}

给定meterid s的特定时段和列表, 什么是最有效的方法来获得每个米 那个时期的第一次和最后一次阅读?

我能够遍历所有仪表并且每米都可以迭代 这段时间的第一次和最后一次阅读, 但是如果有更有效的方法来实现这一点,我就会徘徊。

奖励问题:同样的问题,但有多个时间段来获取数据, 而不只是一个时期。

7 个答案:

答案 0 :(得分:3)

我不确定您希望如何获得此数据,但您可以将其投影为匿名类型:

var metersFirstAndLastReading = meters.Select(m => new 
    {
        Meter = m,
        FirstReading = m.readings.OrderBy(r => r.time).First(),
        LastReading = m.readings.OrderBy(r => r.time).Last()
    });

然后你可以像这样阅读你的结果列表(这个例子仅仅是为了说明):

foreach(var currentReading in metersFirstAndLastReading)
{
    string printReadings = String.Format("Meter id {0}, First = {1}, Last = {2}", 
                               currentReading.Meter.id.ToString(),
                               currentReading.FirstReading.time.ToString(),
                               currentReading.LastReading.time.ToString());

    // Do something...
}

另一种选择是在Meter中创建属性,它会动态返回第一个和最后一个读数:

public class Meter
{
    public int id;
    public List<Reading> readings;

    public Reading FirstReading 
    {
        get
        {
            return readings.OrderBy(r => r.time).First();
        }
    }

    public Reading LastReading
    {
        get
        {
            return readings.OrderBy(r => r.time).Last();
        }
    }
}

编辑:我稍微误解了这个问题。

以下是确定电表的第一个和最后一个读数的实现,包括日期范围(假设meterIdListICollection<int>个ID和begin并且end是指定的日期范围)

var metersFirstAndLastReading = meters
    .Where(m => meterIdList.Contains(m.id))
    .Select(m => new 
    {
        Meter = m,
        FirstReading = m.readings
                        .Where(r => r.time >= begin && r.time <= end)
                        .OrderBy(r => r.time)
                        .FirstOrDefault(),
        LastReading = m.readings
                        .Where(r => r.time >= begin && r.time <= end)
                        .OrderByDescending(r => r.time)
                        .FirstOrDefault()
    });

您现在将无法使用属性(因为您需要提供参数),因此方法可以正常使用:

public class Meter
{
    public int id;
    public List<Reading> readings;

    public Reading GetFirstReading(DateTime begin, DateTime end)
    {
        var filteredReadings = readings.Where(r => r.time >= begin && r.time <= end);

        if(!HasReadings(begin, end))
        {
            throw new ArgumentOutOfRangeException("No readings available during this period");
        }

        return filteredReadings.OrderBy(r => r.time).First();
    }

    public Reading GetLastReading(DateTime begin, DateTime end)
    {
        var filteredReadings = readings.Where(r => r.time >= begin && r.time <= end);

        if(!HasReadings(begin, end))
        {
            throw new ArgumentOutOfRangeException("No readings available during this period");
        }

        return filteredReadings.OrderBy(r => r.time).Last();
    }

    public bool HasReadings(DateTime begin, DateTime end)
    {
        return readings.Any(r => r.time >= begin && r.time <= end);
    }
}

答案 1 :(得分:1)

我有一个非常相似的数据模型,其中此代码用于获取最早的读数,我只是将其更改为包括最新的。

我使用查询语法来执行以下操作:

var query = from reading in db.Readings
            group reading by reading.meterId
            into readingsPerMeter
            let oldestReadingPerMeter = readingsPerMeter.Min(g => g.time)
            let newestReadingPerMeter = readingsPerMeter.Max(g => g.time)
            from reading in readingsPerMeter
            where reading.time == oldestReadingPerMeter || reading.time == newestReadingPerMeter 
            select reading; //returns IQueryable<Reading> 

这将导致每个仪表只有最新和最旧的读数。

我认为这是有效的原因是因为它一次查找数据库以获取每个仪表的所有读数,而不是每个仪表的几次查找。我们有大约40000米,读数约为30mil。我刚刚测试了我们的数据上的查询大约需要10秒

预先形成的sql是每个最小和最大日期的两个子选择之间的交叉连接。

更新:

由于这是可查询的,您应该能够提供一段时间,如下所示:

query.Where(r=>r.time > someTime1 && r.time < someTime2)

或者把它放到原始查询中,我就像它这样分开了。由于我们尚未执行取回数据的操作,因此查询尚未执行。

答案 2 :(得分:0)

创建一个名为Result的返回类型的新类,看起来像这样

public class Result
{
    public int MeterId;
    public Readings Start;
    public Readings Last;
}

我通过制作Meters列表并填充一些数据来模拟您的情况,但您的查询应该几乎相同

var reads = Meters.Where(x => x.readings != null)
                  .Select(x => new Result
                          {
                              MeterId = x.id,
                              Start = x.readings.Select(readings => readings).OrderBy(readings=>readings.time).FirstOrDefault(),
                              Last = x.readings.Select(readings=>readings).OrderByDescending(readings=>readings.time).FirstOrDefault()
                          });

答案 3 :(得分:0)

public IEnumerable<Reading> GetFirstAndLastInPeriod
    (IEnumerable<Reading> readings, DateTime begin, DateTime end)
{
    return
        from reading in readings
        let span = readings.Where(item => item.time >= begin && item.time <= end)
        where reading.time == span.Max(item => item.time) 
            || reading.time == span.Min(item => item.time)
        select reading;            
}

答案 4 :(得分:0)

meters.Where(mt=>desiredMeters.Contains(mt)).Select(mt=>
   new{
     mt.Id,
     First = mt.Readings.Where(<is in period>).OrderBy(rd=>rd.Time).FirstOrDefault(),
     Last = mt.Readings.Where(<is in period>).OrderBy(rd=>rd.Time).LastOrDefault()
   });

如果每米有很多读数,这将不会很好,你应该认为读数是SortedList类。

答案 5 :(得分:0)

我的解决方案将准确返回您想要的(在给定时间段内包含读数的所有米的列表)

public IList<Reading[]> GetFirstAndLastReadings(List<Meter> meterList, DateTime start, DateTime end)
     {       
        IList<Reading[]> fAndlReadingsList = new List<Reading[]>();

            meterList.ForEach(x => x.readings.ForEach(y =>
            {
                var readingList = new List<Reading>();
                if (y.time >= startTime && y.time <= endTime)
                {
                      readingList.Add(y);
                      fAndlReadingsList.Add(new Reading[] { readingList.OrderBy(reading => reading.time).First(), readingList.OrderBy(reading => reading.time).Last() });
                }
            }));

       return fAndlReadingsList;
    }

答案 6 :(得分:0)

我得到了一些非常好的线索,感谢所有响应者。 以下是适用于我的解决方案:

        /// <summary>
        /// Fills the result data with meter readings matching the filters.
        /// only take first and last reading for each meter in period.
        /// </summary>
        /// <param name="intervals">time intervals</param>
        /// <param name="meterIds">list of meter ids.</param>
        /// <param name="result">foreach meter id , a list of relevant meter readings</param>
        private void AddFirstLastReadings(List<RangeFilter<DateTime>> intervals, List<int> meterIds, Dictionary<int, List<MeterReading>> result)
        {
            foreach (RangeFilter<DateTime> interval in intervals)
            {
                var metersFirstAndLastReading = m_context.Meter.Where(m => meterIds.Contains(m.Id)).Select(m => new
                {
                    MeterId = m.Id,
                    FirstReading = m.MeterReading
                                    .Where(r => r.TimeStampLocal >= interval.FromVal && r.TimeStampLocal < interval.ToVal)
                                    .OrderBy(r => r.TimeStampLocal)
                                    .FirstOrDefault(),
                    LastReading = m.MeterReading
                                    .Where(r => r.TimeStampLocal >= interval.FromVal && r.TimeStampLocal < interval.ToVal)
                                    .OrderByDescending(r => r.TimeStampLocal)
                                    .FirstOrDefault()
                });

                foreach (var firstLast in metersFirstAndLastReading)
                {
                    MeterReading firstReading = firstLast.FirstReading;
                    MeterReading lastReading = firstLast.LastReading;

                    if (firstReading != null)
                    {
                        result[firstLast.MeterId].Add(firstReading);
                    }

                    if (lastReading != null && lastReading != firstReading)
                    {
                        result[firstLast.MeterId].Add(lastReading);
                    }

                }

            }
        }


    }