Possible multiple enumeration - jak uniknąć

0

Powiedzmy że mam coś takiego:

class Foo
{
    public int Id { get; set;}
    public string Boo { get; set;}
}

public IEnumerable<Foo> Do(IEnumerable <Foo> foos)
{
    var ids = foos.Select(x => x.Id);
    var cache = api.Get(ids);
    foreach(var foo in foos)
    {
        var someData = cache.TryGetValue(foo.Id);
        if(someData != null)
        {
            foo.Boo = someData;
        }
        else 
        { //something else
        }
     yield return foo;
    }
}

foos jest bardzo ciężkie i zje całą pamięć jak dam .ToList()
R# informuje mnie że: possible multiple enumeration
Api przyjmuje tylko listę id.

Czy można to zrobić jakoś inaczej?

0
public IEnumerable<obj> Do(IEnumerable<int> IDs, int PageSize)

Można wtedy wewnątrz wczytywać dane "partiami". Nie wiem czy idę w dobrym kierunku bo nie za bardzo wiem o co Ci chodzi. gdybyś chciał pobrać kilka tysięcy rekordów to może to będzie dobre?

public class obj
        {
            public obj(int Id, string Value)
            {
                this.Id = Id;
                this.Value = Value;
            }

            public int Id { get; set; }
            public string Value { get; set; }
        }
        public class api
        {
            public static IEnumerable<obj> Get(IEnumerable<int> IDs)
            {
                Random random = new Random();
                foreach (int id in IDs)
                    yield return new obj(id, random.Next(100, 10000).ToString());
            }
        }

        public IEnumerable<obj> Do(IEnumerable<int> IDs, int PageSize)
        {
            int n = 0;
            do
            {
                bool LastLoop = (IDs.Count() <= n + PageSize) ? true : false;
                IEnumerable<int> IDsInCurrentLoop = (LastLoop) ? IDs.ToList().GetRange(n, PageSize) : IDs.ToList().GetRange(n, IDs.Count() - n);
                IEnumerable<obj> importedData = api.Get(IDsInCurrentLoop);
                foreach (obj o in importedData)
                    yield return o;
                n += PageSize;
            } while (n >= IDs.Count());
        }

A najlepiej to w ogóle jak obawiasz się o pamięć to podzielić sobie te ID na partie do pobrania z API i wywoływać Do dla partii danych

0

A tak? :>

     foreach(var foo in foos)
     {
         var cache = api.Get(new List<int> { foo.Id });
         // ...
     }
0
ŁF napisał(a):

A tak? :>

     foreach(var foo in foos)
     {
         var cache = api.Get(new List<int> { foo.Id });
         // ...
     }

No tak to będzie wolał Api w pętli.. chyba nie bardzo

1

Tutaj nie chodzi o to co masz wewnątrz Do(..) tylko gdzie i jak używasz wyników z Do. tzn kiedy te wyniki materializujesz - wtedy idzie faktyczna pętla .
Jak nie zrobisz jawnie ToList to materializacja zrobi się sama przy pierwszym odwołaniu do wyniku.
Komunikat ostrzega o tym że się może zrobić (materializacja - pętla) sama kilka razy np. costam = Do.. i costam2 = Do..
Jeśli masz pewność, że nie - bo tak nie napisałeś - to jest OK.
Jeśli nie, to za pierwszym razem się zmaterializuje i za kolejnym ponownie.

Inna kwestia, że obawiasz się wszystkich wyników z Do(.., ale jeśli to miejsce gdzie wołasz Do(.. nie sprawdza tych częściowych wyników i nie przerywa pętli to i tak dostaniesz wszystkie wyniki - wtedy nie ma sensu yield.
Jeśli natomiast sprawdzasz lub wybierasz wyniki np. Do(..).Where(costam) to jest szansa że pętla będzie krótsza wtedy bym dał ToList() i operował już na tym wyniku.

0

Dużo później w kodzie jest to materializowane w paczkach.

Rozwiązałem problem pobierając wszystkie dane z api (nie jest ich tak dużo).

Zadałem to pytanie dlatego że próbowałem wcześniej rozwiązania @ŁF i działa ono szybciej niż to moje, dlatego pomyślałem że to przez dwie enumeracje, ale może coś źle sprawdzałem :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1