2009-05-19 16 views
5

Zachowuję trochę kodu C# 2.0, a programista używa wzorca do czytania zbioru obiektów biznesowych, otwierając obiekt DataReader, a następnie przekazując go do konstruktora obiektu. Nie widzę w tym nic złego, ale wydaje mi się to złe. Czy to jest w porządku?Czy można przekazać obiekt DataReaders do konstruktorów?

private static void GetObjects() 
{ 
    List<MyObject> objects = new List<MyObject>(); 
    string sql = "Select ..."; 
    SqlConnection connection = GetConnection(); 
    SqlCommand command = new SqlCommand(sql, connection); 
    connection.Open(); 
    SqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection); 
    while (reader.Read()) 
     objects.Add(new MyObject(reader)); 
    reader.Close(); 
} 

public MyObject(SqlDataReader reader) 
{ 
    field0 = reader.GetString(0); 
    field1 = reader.GetString(1); 
    field2 = reader.GetString(2); 
} 
+0

Mój przykład nie był jasny, ale zakładam, że MyObject jest osobną klasą. – Sisiutl

+0

Możesz zmienić swój kod, aby stał się klasą ... :) –

Odpowiedz

5

Przekazując obiekt DataReader do konstruktora obiektu, tworzy się bardzo ścisłe połączenie między obiektem biznesowym a wybraną technologią utrwalania.

Przynajmniej te ścisłe sprzężenie utrudni ponowne użycie i testowanie; w najgorszym przypadku może to spowodować, że obiekty biznesowe będą wiedziały o zbyt wiele na temat bazy danych.

Rozwiązanie tego nie jest zbyt trudne - wystarczy przenieść inicjalizację obiektu z konstruktora do odrębnej klasy fabrycznej.

+3

Myślę, że zmiana SqlDataReader na IDataReader w konstruktorze metod znacznie utrudnia to naprawienie. IDataReader jest na tyle ogólny, że nie można go powiązać z żadną podstawową technologią utrwalania. – TheSoftwareJedi

+0

Nie całkowicie się zgadzam - podczas gdy uzyskujesz pewne oddzielenie, ponieważ nie jesteś już zależny od konkretnej implementacji, przekazując IDataReader wciąż przywiązuje cię do używania surowego ADO.NET, żyjąc tak jak w przestrzeni nazw System.Data, wykluczające użycie czegokolwiek, co zapewnia wyższą abstrakcję (NHibernate, Entity Framework, Lightspeed, Genome, etcetera itp.). – Bevan

+0

'IDataReader' jest dość skomplikowany - nie wygrywasz prawie nic, używając go zamiast SqlDataReader. Jedyną alternatywą dla 'IDataReader' jest przenośność do innego dostawcy ADO.NET, który jest mostem, który równie dobrze możesz przekazać, gdy go osiągniesz - ten konkretny interfejs będzie najmniejszym z twoich problemów. –

1

Nie chciałbym zrobić to w ten sposób, ale nie widzę niczego Majorly złego.

3

Przekazywanie czytelnikowi sprawia, że ​​wzdragam się, chyba że jest to niepubliczna metoda pomocnicza do radzenia sobie z kopiowaniem jednego wiersza. Zdecydowanie nie do konstruktora.

Przekazanie czytnika łączy twojego konstruktora (nie umieściłeś MyObject w klasie, ale wywołujesz new MyObject()) do przechowywania danych i zakładam, że twój obiekt nie jest tak napisany?

Gdyby mnie:

private static void GetObjects() 
{ 
    List<MyObject> objects = new List<MyObject>(); 
    string sql = "Select ..."; 
    using (SqlConnection connection = GetConnection()) 
    { 
     SqlCommand command = new SqlCommand(sql, connection); 
     connection.Open(); 
     using(SqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);) 
     { 
      while (reader.Read()) 
       objects.Add(_ReadRow(reader)); 
     } 
    } 
} 

private static MyObject _ReadRow(SqlDataReader reader) 
{ 
    MyObject o = new MyObject(); 
    o.field0 = reader.GetString(0); 
    o.field1 = reader.GetString(1); 
    o.field2 = reader.GetString(2); 

    // Do other manipulation to object before returning 

    return o; 
} 

class MyObject{} 
+0

to jest prawie na pewno to, co bym zrobił - jestem pewien, że ktoś pokaże sposób użycia anonimowych metod lub lambdas. Ciekaw jestem, jak "popularne" staje się to rozwiązanie. – MikeJ

+0

"używanie (czytnik SqlDataReader ..." nie jest tak naprawdę potrzebne, prawda? Ponieważ połączenie jest już opakowane, utylizacja tego spowoduje wyczyszczenie poleceń, kursorów, itp. – TheSoftwareJedi

+2

Jeśli klasa implementuje IDisposable, to chce zadzwoń do Dispose, nie rozczaruj się, –

1

(EDIT: Ta odpowiedź skupia się wyłącznie na „jakie są implikacje na stosunkowo niskim poziomie”, a nie ogólnych skutków projektowych wygląda jak inne odpowiedzi mam. te objęte, więc nie będę komentować :)

Coś jest nie tak z kodem, który podałeś, ponieważ nic nie zamyka połączenia, polecenia lub czytnika. W szczególności, linia zadanie połączenie powinno zazwyczaj wygląda tak:

using (SqlConnection connection = GetConnection()) 
{ 
    ... 
} 

Być może dobrze, że to jest właśnie nit-picking, i że to tylko przykładowy kod i czyste-up ma znaczenia - ale „własności” Zasoby, które potrzebują do czyszczenia, to właśnie problem z przekazaniem do konstruktora DataReader.

Myślę, że to w porządku, o ile dokumentujesz, kto "jest właścicielem" czytnika. Na przykład, w Image.FromStream, obraz jest właścicielem strumienia i może nie być przyjazny dla Ciebie zamykając go sam (w zależności od formatu obrazu i kilku innych rzeczy). Innym razem to twój obowiązek zamknąć. Musi to być bardzo starannie udokumentowane, a jeśli typ z konstruktorem stanie się własnością, powinien wdrożyć , aby oczyścić je łatwiejsze: i, aby było bardziej oczywiste, że wymagane jest czyszczenie.

W twoim przypadku wygląda na to, że konstruktorem jest , a nie przejmowanie na własność czytnika, co jest doskonałą (i prostszą) alternatywą. Po prostu dokumentuj to, a osoba dzwoniąca będzie nadal musiała odpowiednio zamknąć czytnik.

+1

+1 dla ZAMKNIJ POŁĄCZENIE. Użyj klauzuli "używanie"! – TheSoftwareJedi

+0

Zastrzeżenia do edycji? czy dodałem za dużo? :) – TheSoftwareJedi

+0

Grzywna przeze mnie - dodałem "zwykle", ponieważ są rzadkie przypadki, kiedy kończy się to nieco inaczej, ale zwykle jest to sposób użycia. –

1

Sprawię, że podmiot zostanie przekazany w IDataReader, ponieważ pomoże to w testowaniu MyObject.

+0

Z ciekawości, czy próbowałeś tego? Zacząłem od tej ścieżki i chociaż wyglądało to * możliwe * IDataReader to bardzo duży interfejs z nieprzyjemnymi bitami, jak "GetSchemaTable". Twój kod testowy będzie duży, złożony i trudny do utrzymania. A większość kodu, który będziesz testować, będzie ściśle powiązana z rzeczywistymi bazami danych sql - i prawie wszystkie błędy będą w tym sprzężeniu (transakcje, zapytania, czas itd.). Uznałem, że i tak nie warto. –

+0

Dobry komentarz. Nie, nie mam. –

1

Nazwałbym to "nieszczelną abstrakcją".

Lubię używać obiektów uporczywości w możliwie najwęższym zakresie: je zdobywać, używać ich, czyścić. Jeśli istnieje dobrze zdefiniowany obiekt utrwalania, możesz poprosić go o odwzorowanie wyniku zapytania na obiekt lub kolekcję, zamknij czytnik w zasięgu metody i zwróć obiekt lub kolekcję klientowi.

0

I całkowicie zgadzam się z duffymo (+1) (i Bevan)

Pomysł I zostały żucia nad niedawno w moim umyśle jest, czy muszę mieć zależność, i oczywiście kiedy zmapować czytnik do obiektu muszę mieć zależność, może powinienem zrobić to całkowicie jednoznaczne i faktycznie napisać metodę rozszerzenia, aby go osiągnąć, coś jak ...

//This Static extension class in the same namespace (but not necesarrily assembly) as my BO 

public static class DataReaderExtensions 
{ 
    public static List<MyObject> GetMyObjects(this DataReader reader) 
    { 

    } 
} 

w ten sposób, tak długo, jak jestem w zakres referencyjny mojego obiektu biznesowego, mój datareader będzie teraz miał metodę dostosowaną do moich potrzeb ... pomysł prawdopodobnie musi zostać ulepszony bardziej, ale myślę, że byłoby to eleganckie.

0

Do oddzielnych warstwach biznesowych i danych używać WYDAJNOŚĆ

public IEnumerable<IDataReader> getIDataReader(string sql) 
{ 

    using (SqlConnection conn = new SqlConnection("cadena de conexion")) 
    { 
     using (SqlCommand da = new SqlCommand(sql, conn)) 
     { 
      conn.Open(); 
      using (SqlDataReader dr = da.ExecuteReader) 
      { 
       if (dr.HasRows) 
       { 
        while (dr.Read) 
        { 
         yield return dr; 
        } 
       } 
      } 
      conn.Close(); 
     } 
    } 
} 
0

Przechodząc czytnik danych do konstruktora może być dyskusyjna, ale - o ile wiem - jest dobrze znane podejście. Ale ktokolwiek go używa, powinien przekazać abstrakcję, tj. Nie SqlDataReader, ale IDataReader. Jednak IDataReader nie jest optymalny; powinien to być IDataRecord, który zawiera wynik dla danego rekordu, zamiast pozwalać na iterację !!

Powiązane problemy