2009-09-09 14 views
5

Moje pytanie dotyczące statusu połączenia SQL, obciążenia itp na podstawie następującego kodu:Czy są jakieś pułapki związane z używaniem typu IEnumerable <T> dla danych SQL?

public IEnumberable<MyType> GetMyTypeObjects() 
{ 
    string cmdTxt = "select * from MyObjectTable"; 

    using(SqlConnection conn = new SqlConnection(connString)) 
    { 
    using(SqlCommand cmd = new SqlCommand(cmdTxt, conn)) 
    { 
     conn.Open(); 
     using(SqlDataReader reader = cmd.ExecuteReader()) 
     { 
     while(reader.Read()) 
     { 
      yield return Mapper.MapTo<MyType>(reader); 
     } 
     } 
    } 
    } 
    yield break; 
} 

widzę to może być problemem, jeśli istnieje wiele uruchomione procesy podobny kod z długich czasów wykonania między iteracji obiekt IEnumerable, ponieważ połączenia będą otwierane dłużej, itp. Wydaje się jednak, że może to zmniejszyć użycie procesora na serwerze SQL, ponieważ zwraca dane tylko wtedy, gdy używany jest obiekt IEnumerable. Obniża także użycie pamięci na kliencie, ponieważ klient musi załadować tylko jedną instancję MyType, podczas gdy działa, zamiast ładowania wszystkich wystąpień MyType (przez iterowanie przez cały obiekt DataReader i zwracanie listy lub coś podobnego).

  • Czy istnieją przypadki, można myśleć, gdzie nie chcesz używać IEnumerable w ten sposób, czy jakiekolwiek przypadki uważasz, że doskonale pasuje?

  • Jaki rodzaj obciążenia to stawia na serwerze SQL?

  • Czy jest to coś, co można użyć we własnym kodzie (z wyjątkiem jakiejkolwiek wzmianki o NHibernate, Subsonic, itp.)?

  • -

Odpowiedz

2

Nie użyłbym tego, ponieważ ukrywa to, co się dzieje, i może pozostawić połączenia z bazą danych bez właściwej utylizacji.

Obiekt połączenia zostanie zamknięty po przeczytaniu ostatniego rekordu, a jeśli przerwie czytanie przed tym obiekt połączenia nie zostanie usunięty. Jeśli na przykład wiesz, że zawsze masz dziesięć rekordów w wyniku i po prostu masz pętlę, która odczytuje te dziesięć rekordów z modułu wyliczającego, nie wykonując jedenastego polecenia Odczytaj, które czyta poza ostatnim elementem, połączenie nie jest poprawnie zamknięte. Ponadto, jeśli chcesz użyć tylko części wyniku, nie masz możliwości zamknięcia połączenia bez czytania reszty rekordów.

Nawet zbudowany w rozszerzeń dla rachmistrzów może powodować nawet jeśli seamlingy ich używać prawidłowo:

foreach (MyType item in GetMyTypeObjects().Take(10)) { 
    ... 
} 
+0

To dobra uwaga, również tego nie uważałem. Jestem tak zaślepiony tym, jak bym go użył! – scottm

+1

@Guffa: Używanie metod rozszerzających, takich jak 'Take', nie byłoby problemem: metoda' GetMyTypeObjects' jest po prostu syntaktycznym cukrem, który tworzy obiekt iteratora 'IDisposable'. 'Take' wywoła metodę iteratora' Dispose', kiedy to zostanie zrobione, a następnie pozbędzie się połączenia, polecenia, czytnika itp. – LukeH

+0

@Guffa: To samo dotyczy innych częściowych odczytów zestawu wyników: Tak długo, jak wywołaj polecenie "Dispose", kiedy skończysz (lub najlepiej po prostu zapakuj wszystko w blok 'using'), a następnie połączenie, polecenie, czytnik itp. zostaną również usunięte. – LukeH

2

Polecam przed wstępną optymalizacją. W wielu sytuacjach połączenia będą łączone.

Nie oczekuję również żadnej różnicy w obciążeniu serwera SQL - zapytanie zostanie już skompilowane i będzie działać.

+0

nie jestem pewien co rozumiesz przez wstępną optymalizację (nie próbuję). Myślałem, że wpłynie to na obciążenie, ponieważ serwer utrzymywałby pozycję kursora na większej liczbie połączeń naraz. – scottm

+0

Przez wstępną optymalizację mam na myśli to, że martwisz się problemami z wydajnością, zanim Twój kod działa. Myślisz o problemach, które mogą nie istnieć. –

+0

Rozumiem. Nie chcę też zakodować siebie w kącie, musiałem przeprojektować moje metody dostępu do danych i metody, które wykorzystują je w przyszłości. – scottm

6

To nie jest wzór, który powinienem wykonać. Nie martwiłbym się tak bardzo o ładowanie na serwerze, co o blokadach. Podążanie za tym wzorcem integruje proces pobierania danych z przepływem logiki biznesowej i wydaje się, że jest to wszechstronna recepta na kłopoty; nie masz pojęcia, co dzieje się po stronie iteracji, i wkładasz w to siebie. Odzyskaj dane za jednym razem, a następnie zezwól na wyliczenie kodu klienta po zamknięciu czytnika.

+0

Blokady obiektów SQL? Nie brałem tego pod uwagę. Co myślisz o udzieleniu podpowiedzi nolock (która byłaby możliwa tylko w pewnych okolicznościach)? – scottm

+1

To by (oczywiście) wyeliminowało problem z blokadą, ale nadal utrzymujesz czytnik otwarty przez nieokreślony czas (być może na zawsze, jeśli kod zużywający zapomni wyrzucić moduł wyliczający). To po prostu wydaje się stosunkowo dużym ryzykiem niskiego zysku. Jeśli istnieje szczególna potrzeba takiej praktyki - przypuszczam, że zużycie pamięci może się kwalifikować - wtedy nie jest to absolutnie złe, ale na pewno nie stałoby się to nawykiem i szukałbym alternatyw. –

+0

A na marginesie, nie gwarantuje się "jednej instancji MyType". Rzeczywiście, nawet jeśli kod konsumujący dotyczy tylko jednej instancji na raz, GC nie zamierza ich natychmiast oczyścić. Tak, spowodujesz, że wypadną one poza zasięgiem i będą szybciej się zbierać, ale nie będzie to stały ślad pamięci. –

1

Moim problemem jest to, że jesteś oddanie się całkowicie na łasce kodu klienckiego.

Wspomniałeś już, że kod wywołujący może utrzymywać połączenie dłużej otwarte, niż jest to absolutnie konieczne, ale istnieje również możliwość, że nigdy nie pozwoli na zamknięcie/usunięcie obiektów.

Dopóki kod klienta używa foreach, using etc lub wyraźnie wzywa Dispose metodę wyliczający za to jesteś w porządku, ale nie ma nic, aby zatrzymać go robi coś takiego:

var e = GetMyTypeObjects().GetEnumerator(); 
e.MoveNext(); // open the connection etc 

// forget about the enumerator and go away and do something else 
// now the reader, command and connection won't be closed/disposed 
// until the GC kicks in and calls their finalisers 
Powiązane problemy