9

のエンティティ コレクションがあり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のリストが与えられた場合、各メーターについてその期間の最初と最後の読み取り値を取得する最も効率的な方法は何でしょうか?

私はすべてのメーターを反復処理し、各メーターでその期間の最初と最後の読み取りを取得することができますが、これを達成するためのより効率的な方法があるかどうかをさまよっていました.

おまけの質問: 同じ質問ですが、データを取得する期間が 1 つだけではなく、複数の期間があります。

4

7 に答える 7

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

もう 1 つのオプションは、最初と最後の読み取り値を動的に返す 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 の でありbeginendは指定された日付範囲であると仮定します)。

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);
    }
}
于 2013-05-27T07:32:58.637 に答える
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> 

これにより、各メーターの最新および最古の測定値のみが得られます。

これが効率的だと思う理由は、メーターごとに複数回検索するのではなく、DB を 1 回検索して各メーターのすべての測定値を取得するためです。測定値が 30mil までの 40000 メートルがあります。データのルックアップをテストしたところ、約 10 秒かかりました

実行される SQL は、最小日付と最大日付のそれぞれに対する 2 つのサブ選択の間のクロス結合です。

アップデート:

これはクエリ可能であるため、次のように後にピリオドを指定できるはずです。

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

または、元のクエリに入れます。このように区切られているのが好きです。データを取得するアクションをまだ実行していないため、クエリはまだ実行されていません。

于 2013-05-27T09:25:55.607 に答える
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);
                    }

                }

            }
        }


    }
于 2013-05-28T08:37:33.550 に答える
0

という戻り値の型として新しいクラスを作成しますResult。これは次のようになります。

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

メーターのリストを作成していくつかのデータを入力することで状況をエミュレートしましたが、クエリはほとんど同じはずです

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()
                          });
于 2013-05-27T07:33:26.827 に答える