高效的LINQ到实体查询
本文关键字:实体 查询 LINQ 高效 | 更新日期: 2023-09-27 18:15:42
我有一个Readings
的实体集合。每个Reading
都链接到一个名为Meter
的实体。(每个Meter
保存多个读数(。每个CCD_ 5保持用于仪表id(int(的字段和用于时间的字段。
这里有一些简化的代码来演示它:
public class Reading
{
int Id;
int meterId;
DateTime time;
}
public class Meter
{
int id;
ICollection<Readings> readings;
}
给定特定周期和CCD_ 6s的列表,对于每个电表,最有效的方法是什么那个时期的第一次也是最后一次阅读?
我能够遍历所有仪表,并为每一个仪表创建obatin该时段的第一次和最后一次读取,但我在想是否有更有效的方法来实现这一点。
还有一个奖励问题:相同的问题,但需要多个时间段来获取数据,而不是仅仅一个周期。
我不确定你想要如何获得这些数据,但你可以将其投影到匿名类型中:
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();
}
}
}
编辑:我有点误解了这个问题
以下是确定仪表的第一个和最后一个读数的实现,包括日期范围(假设meterIdList
是ID的ICollection<int>
,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);
}
}
我有一个非常相似的数据模型,其中该代码用于获取最旧的读数,我只是将其更改为也包括最新的读数。
我使用查询语法来做这样的事情:
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)
或者把它放在原始查询中,我只是喜欢它像这样分开。查询还没有执行,因为我们还没有执行获取数据的操作。
创建一个名为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()
});
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;
}
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类。
我的解决方案将准确返回您想要的(给定时间段内包含读数的所有仪表的列表(
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;
}
我得到了一些非常好的线索,感谢所有的响应者。以下是对我有效的解决方案:
/// <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);
}
}
}
}
}