Эфектыўнае LINQ да Entities запыту

У мяне ёсць калекцыя Суб'ект </чытання кода>. Кожны Чытанне звязаны з арганізацыяй пад назвай Метр . (І кожны Метр змяшчае некалькі чытанняў). кожны Чытанне змяшчае поле для метровага ідэнтыфікатара (INT) і палі для часу.

Вось некалькі спрошчанага кода, каб прадэманстраваць гэта:

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

public class Meter
{
    int id;
    ICollection readings;    
}

Улічваючы пэўны перыяд і спіс meterid з, што будзе найбольш эфектыўным спосабам, каб атрымаць для кожнага вымяральнага прыбора першае і апошняе чытанне ў гэты перыяд часу?

Я магу перабрацца ўсе метр і за кожны метр у obatin першае і апошняе чытанне за перыяд, але я блукаў, калі ёсць больш эфектыўны спосаб дамагчыся гэтага.

І <�моцны> бонус пытанне : тое ж пытанне, але з некалькімі перыядамі часу, каб атрымаць дадзеныя для, а не толькі адзін перыяд.

9
Так, маё рашэнне для кожнага лічыльніка і перыяду заняць першае() і Last() - але гэта не прымаць да ўвагі той факт, што для ўсіх метраў я, гледзячы на ​​той жа перыяд часу. можа быць, нейкая групоўка была б больш эфектыўнай тут?
дададзена аўтар omer schleifer, крыніца
Вы спрабавалі Queryable.First() і Queryable.Last() ?
дададзена аўтар user1914530, крыніца

7 адказы

Я дакладна не ведаю, як вы хочаце гэтыя дадзеныя, але вы можаце праецыраваць яго ў ананімны тып:

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

Іншым варыянтам было б стварыць ўласцівасці ў метры, які дынамічна вяртаюць першы і апошні паказанні:

public class Meter
{
    public int id;
    public List readings;

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

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

EDIT: I misunderstood the question a little.

Here is the implementation to determine the first and last readings for a meter including a date range (assuming meterIdList is an ICollection of IDs and begin and end is the specified date range)

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 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);
    }
}
3
дададзена
@davenewza, я ў канчатковым выніку з дапамогай раствора metersFirstAndLastReading (другі пасля «незразумелы» pharse. Гэта дапамагло. Калі вы будзеце так ласкавы, каб выправіць дзве рэчы, гэта, я gldaly пазначыць яго як адказ. 1. Апошняя() не з'яўляецца сапраўдным. замест гэтага вам трэба замовіць па змяншэнні і абярыце First() (ці яшчэ лепш FirstOrDefault ()). 2. фільтраванне па метровых ідэнтыфікатарамі адсутнічае. Я дадам код, які працаваў на маё пытанне. Вітаньні :-)
дададзена аўтар omer schleifer, крыніца
@Yes, калі ласка, паглядзіце на мой пост, ён кажа: «улічваючы перыяд часу" ... Але ў любым выпадку, мой пытанне аб прадукцыйнасці, будзе ваш запыт павысіць прадукцыйнасць у параўнанні з маім «наіўнай» рашэнні? калі так - маглі б вы exlpain чаму? дзякуй
дададзена аўтар omer schleifer, крыніца
@davenewza, дзякуй за Ваш адказ. фільтраванне па часе не патрабуецца. але ў любым выпадку, я не ўпэўнены, калі гэта прывядзе да павелічэння прадукцыйнасці. яна па-ранейшаму праходзіць праз кожны метр, і просіць першыя і апошнія, ці не так?
дададзена аўтар omer schleifer, крыніца
@omer: Прашу прабачэння. Дададзены код! Я не ўпэўнены, што ваш «наіўным» рашэнне, як я не бачу якой-небудзь з вашага кода
дададзена аўтар davenewza, крыніца
@omer: У вас ёсць трохі поспеху? :)
дададзена аўтар davenewza, крыніца
@omer: задавальненне! Вы хочаце, каб карыстальнік, каб паказаць дыяпазон часу для вызначэння першага і апошняга паказанні?
дададзена аўтар davenewza, крыніца
@omer: Абноўлены код з meterIdList і выправілі OrderByDescending частка (упс!). Калі вы хочаце выкарыстоўваць First() або FirstByDefault() залежыць ад характару вашых дадзеных. Вы вызначана хочаце, каб ваш код згенеруе выключэнне, калі дадзеныя, безумоўна, чакаецца, - гэта залежыць ад вас :)
дададзена аўтар davenewza, крыніца
<�Код> Чытанне s належаць Meter , так што вам не трэба выконваць фільтрацыю.
дададзена аўтар davenewza, крыніца
Вам трэба дадаць у «дзе» палажэнняў прымаць пад увагу перыяд часу
дададзена аўтар jammykam, крыніца

У мяне ёсць вельмі падобная мадэль дадзеных, дзе гэты код выкарыстоўваецца для атрымання самых старых паказанняў, я проста змяніў яго таксама ўключае ў сябе новы.

Я выкарыстоўваю сінтаксіс запыту, каб зрабіць нешта накшталт гэтага:

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 

Гэта прывяло б да толькі найноўшаму і найстарэйшаму чытання для кожнага метра.

Прычына я думаю, што гэта з'яўляецца эфектыўным, таму што яго адзін пошук у БД, каб атрымаць усе паказанні для кожнага метра, замест некалькіх выбарак для кожнага лічыльніка. У нас ёсць ~ 40000 метраў з ~ чытання 30mil. я проста праверыў пошук па нашых дадзеных яна займае каля 10 секунд

SQL Сфармаваць з'яўляецца декартовых паміж два субом выбірае для кожнага з мінімальных і максімальных тэрмінаў.

UPDATE:

Паколькі гэта Дзяржава, якую запрошваюць вы павінны быць у стане паставіць кропку пасля, як гэта:

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

Або змясціць яго ў зыходны запыт, я так жа, як гэта аддзеленае, як гэта. Хіба запыт выконваецца яшчэ, так як мы нету выканалі дзеянне, якое здабывае дадзеныя яшчэ.

1
дададзена
Ды я магу бачыць, праблема складаецца ў тым гэта вяртае адзін радок з абодвух вынікаў, пры перакладзе на SQL, замест радка для кожнага выніку (мін і макс)
дададзена аўтар Jim Wolff, крыніца
Абноўлена для дадання перыяду часткі, яна прыме новае і найстарэйшае чытанне на працягу перыяду для кожнага метра.
дададзена аўтар Jim Wolff, крыніца
гэта, здаецца, для павышэння прадукцыйнасці. Але за кожны метр дае толькі адзін чытанне, а не два. любая ідэя аб тым, як атрымаць і першае аб'яву ў апошні раз? дзякуй
дададзена аўтар omer schleifer, крыніца
@Thanks Калі ласка, звярніце ўвагу, што: а. гэта не фільтр па часе. б. яна вяртае першае або апошняе чытанне, а не абодва. але я думаю, што я атрымліваю гэтую ідэю.
дададзена аўтар omer schleifer, крыніца

my solution will return exact what u want (List of all Meters containing Readings within given Time Period)

public IList GetFirstAndLastReadings(List meterList, DateTime start, DateTime end)
     {       
        IList fAndlReadingsList = new List();

            meterList.ForEach(x => x.readings.ForEach(y =>
            {
                var readingList = new List();
                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;
    }
0
дададзена

Я атрымаў некаторыя вельмі добрыя драты, дзякуй усім респондеров. Вось рашэнне, якое працуе для мяне:

        /// 
/// Fills the result data with meter readings matching the filters. /// only take first and last reading for each meter in period. ///
 
        /// 
time intervals
        /// 
list of meter ids.
        /// 
foreach meter id , a list of relevant meter readings
        private void AddFirstLastReadings(List> intervals, List meterIds, Dictionary> result)
        {
            foreach (RangeFilter 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);
                    }

                }

            }
        }


    }
0
дададзена
meters.Where(mt=>desiredMeters.Contains(mt)).Select(mt=>
   new{
     mt.Id,
     First = mt.Readings.Where().OrderBy(rd=>rd.Time).FirstOrDefault(),
     Last = mt.Readings.Where().OrderBy(rd=>rd.Time).LastOrDefault()
   });

Калі ў вас ёсць шмат паказанняў на метр, гэта не будзе добра, і вы павінны разгледзець чытання, каб мець клас SortedList.

0
дададзена

Стварыце новы клас у якасці якое вяртаецца тыпу называецца Вынік , які выглядае наступным чынам

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()
                          });
0
дададзена
Або OP можна выкарыстоўваць ананімныя тыпы.
дададзена аўтар davenewza, крыніца
Так, я проста аддаю перавагу набраныя, але хутка гэта таксама добра. Я думаю, выкарыстоўваючы тып якое вяртаецца дадае крыху яснасці.
дададзена аўтар James, крыніца
public IEnumerable GetFirstAndLastInPeriod
    (IEnumerable 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;            
}
0
дададзена