2009-01-12 5 views
8

In letzter Zeit zu verbessern, finde ich mich Datenzugriffsschicht Auswahlmethoden zu schreiben, wo der Code alle diese allgemeine Form annimmt:Wie Datenzugriffsschicht Auswahlmethode Muster

public static DataTable GetSomeData(... arguments) 
{ 
    string sql = " ... sql string here: often it's just a stored procedure name ... "; 

    DataTable result = new DataTable(); 

    // GetOpenConnection() is a private method in the class: 
    // it manages the connection string and returns an open and ready connection 
    using (SqlConnection cn = GetOpenConnection()) 
    using (SqlCommand cmd = new SqlCommand(sql, cn)) 
    { 
     // could be any number of parameters, each with a different type 
     cmd.Parameters.Add("@Param1", SqlDbType.VarChar, 50).Value = param1; //argument passed to function 

     using (SqlDataReader rdr = cmd.ExecuteReader()) 
     { 
      result.Load(rdr); 
     } 
    } 

    return result; 
} 

Oder wie folgt aus:

public static DataRow GetSomeSingleRecord(... arguments) 
{ 
    string sql = " ... sql string here: often it's just a stored procedure name ... "; 

    DataTable dt = new DataTable(); 

    // GetOpenConnection() is a private method in the class: 
    // it manages the connection string and returns an open and ready connection 
    using (SqlConnection cn = GetOpenConnection()) 
    using (SqlCommand cmd = new SqlCommand(sql, cn)) 
    { 
     // could be any number of parameters, each with a different type 
     cmd.Parameters.Add("@Param1", SqlDbType.VarChar, 50).Value = param1; //argument passed to function 

     using (SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.SingleRow)) 
     { 
      dt.Load(rdr); 
     } 
    } 

    if (dt.Rows.Count > 0) 
     return dt.Rows[0]; 
    return null; 
} 

These Methoden werden von Business-Layer-Code aufgerufen, der die DataTable- oder DataRecord-Basis in stark typisierte Geschäftsobjekte konvertiert, die von der Präsentationsschicht verwendet werden können.

Da ich wiederholt ähnlichen Code verwende, möchte ich sicherstellen, dass dieser Code der beste Code ist. Wie kann es verbessert werden? Und es lohnt sich zu versuchen, den gemeinsamen Code von diesem auf seine eigene Methode zu übertragen. Wenn ja, wie würde diese Methode aussehen (insbesondere hinsichtlich der Übergabe einer SqlParameter-Sammlung)?

+0

Sie tun dies, genau wie ich - obwohl ich mag wirklich, wie Sie Ihre Dual-using-Anweisungen stapeln die tiefe Verschachtelung zu vermeiden. gut gemacht! eine Sache, die mich interessiert, ist, wie Sie Ihre DAL logistisch/phyiscally vs. your BIZ einrichten - setzen Sie das in ein eigenes Projekt? oder als Namensraum? oder was? – dfasdljkhfaskldjhfasklhf

+0

Wenn keine Parameter vorhanden sind, können Sie den Datenreader ebenfalls stapeln. DAL ist in seiner eigenen Assembly, DAL + BL teilt sich einen gemeinsamen Parent-Namespace. –

+0

Ja, so mache ich es jetzt. Wie sieht Ihr Mapping zwischen Business-Objekten und DAL-Objekten aus? also gehst du 1: 1? Eine Sache, die ich interessant fand, war jemand, der die DAL in das Geschäftsobjekt gezogen und alles mit CodeSmith generiert hat. Sehr interessant – dfasdljkhfaskldjhfasklhf

Antwort

2

Ein Muster, das ich genossen habe sieht wie folgt aus so weit wie Client-Code lautet:

 DataTable data = null; 
     using (StoredProcedure proc = new StoredProcedure("MyProcName","[Connection]")) 
     { 
      proc.AddParameter("@LoginName", loginName); 
      data = proc.ExecuteDataTable(); 
     } 

ich in der Regel Anschluss optional machen, und ich werde in einer Weise codieren es von Connection Config Abschnitt zu ziehen oder sie behandeln als die tatsächliche Verbindungszeichenfolge. Dadurch kann ich den dal in einem einmaligen Szenario wiederverwenden und ist teilweise ein Habbit von den COM + -Tagen, als ich die Verbindungszeichenfolge unter Verwendung der Objektkonstruktionseigenschaft gespeichert habe.

Ich mag dies, weil es einfach zu lesen ist und verbirgt den ganzen ADO-Code von mir.

+0

Ich habe einige Probleme damit, aber Upvote, weil es mir eine Idee gab: Ich werde damit spielen, den gemeinsamen Code als Functor und nicht als Methode zu verstecken. –

+0

Interessant teilen Sie bitte mit, was Sie kommen. Ich bin nicht auf dieses Muster verkauft, es ist nur so weit wie ich 2.0 bekam. Und es reduziert das Durcheinander – JoshBerke

+0

Beachten Sie, dass ich "play" sagte: Ich glaube nicht, dass ich am Ende diesen Weg gehen werde. Wenn ich mir etwas Interessantes einfallen lasse, poste ich es zurück. –

1

Es gibt so viele Möglichkeiten, die DBAL zu implementieren, meiner Meinung nach sind Sie auf dem richtigen Weg. Somethings in Ihrer Implementierung zu berücksichtigen:

  • Sie sind ab Werk eines ähnliches Verfahren mit Ihrem SqlConnection zu erstellen, es ist ein kleiner Punkt, aber Sie das gleiche für Ihren SqlCommand tun können.
  • Die Parameterlänge ist optional, Sie können sie also aus dem Parameter.Add-Aufruf herauslassen.
  • Erstellen Sie auch Methoden zum Hinzufügen von Parametern, Codebeispiel unten.

hinzufügen Parametern DbUtil.AddParameter(cmd, "@Id", SqlDbType.UniqueIdentifier, Id);

internal class DbUtil { 

internal static SqlParameter CreateSqlParameter(
    string parameterName, 
    SqlDbType dbType, 
    ParameterDirection direction, 
    object value 
) { 
    SqlParameter parameter = new SqlParameter(parameterName, dbType); 

    if (value == null) { 
     value = DBNull.Value; 
    } 

    parameter.Value = value; 

    parameter.Direction = direction; 
    return parameter; 
} 

internal static SqlParameter AddParameter(
    SqlCommand sqlCommand, 
    string parameterName, 
    SqlDbType dbType 
) { 
    return AddParameter(sqlCommand, parameterName, dbType, null); 
} 

internal static SqlParameter AddParameter(
    SqlCommand sqlCommand, 
    string parameterName, 
    SqlDbType dbType, 
    object value 
) { 
    return AddParameter(sqlCommand, parameterName, dbType, ParameterDirection.Input, value); 
} 

internal static SqlParameter AddParameter(
    SqlCommand sqlCommand, 
    string parameterName, 
    SqlDbType dbType, 
    ParameterDirection direction, 
    object value 
) { 
    SqlParameter parameter = CreateSqlParameter(parameterName, dbType, direction, value); 
    sqlCommand.Parameters.Add(parameter); 
    return parameter; 
    } 
} 
1

Erstens, ich glaube, Sie bereits ein ORM betrachtet mit gegen Ihre eigenen Rollen. Ich werde darauf nicht eingehen.

Meine Gedanken über Ihren eigenen Datenzugriffscode rollen:

  • Im Laufe der Zeit fand ich es einfacher, nicht getrennt DAL/BL Objekte zu haben, sondern sie zu einem einzigen Objekt zu verschmelzen (einige Zeit später nach dem Erreichen Diese Schlussfolgerung fand ich heraus, es ist ein ziemlich bekanntes Muster - nämlich ActiveRecord). Es sieht vielleicht gut aus und entkoppelt, um separate DAL-Baugruppen zu haben, aber der Overhead in den Wartungskosten summiert sich. Jedes Mal, wenn Sie eine neue Funktion hinzufügen, müssen Sie mehr Code erstellen/weitere Klassen modifizieren.Nach meiner Erfahrung ist das Team, das die Anwendung verwaltet, oft viel weniger als das ursprüngliche Entwicklerteam, das es erstellt hat, und sie hassen die zusätzliche Arbeit, die erforderlich ist.
  • Für große Teams, könnte es sinnvoll, die DAL zu trennen (und eine Gruppe arbeiten daran lassen, während die andere, aber dies für Code aufblasen einen guten Anreiz macht
  • kommend nach unten auf Ihr spezielles Beispiel:.., Wie Sie tut Verwenden Sie die resultierende DataTable? Iterieren Sie die Zeilen, erstellen Sie typisierte Objekte und erhalten Sie die Daten aus der Zeile? Wenn die Antwort ja ist, denken Sie an die extra DataTable, die Sie nur zum Verschieben von Daten zwischen der DAL und der BL erstellt. Warum nicht direkt aus der DataReader?
  • Auch über das Beispiel: Wenn Sie eine untypisierte DataTable zurückgeben, dann denke ich, müssen Sie die Spaltennamen (der Ergebnismenge, die der SP-Aufruf zurückgibt) weit oben in der aufrufenden Code Um etwas in der Datenbank zu ändern, kann es beide Ebenen betreffen.

Mein Vorschlag (Ich habe beide Methoden ausprobiert - der Vorschlag ist der neueste Arbeitsansatz, den ich entwickelt habe - er hat sich im Laufe der Zeit entwickelt).

  • Erstellen Sie eine Basisklasse für Ihre typisierten Geschäftsobjekte.
  • Behalten Sie den Objektstatus in der Basisklasse (neu, modifiziert usw.)
  • Legen Sie die Hauptdatenzugriffsmethoden in dieser Klasse als statische Methoden fest. Mit ein wenig Aufwand (Hinweis: generische Methoden + Activator.CreateInstance) können Sie für jede im Lesegerät zurückgegebene Zeile ein Business-Objekt erstellen.
  • Erstellen Sie eine abstrakte Methode im Geschäftsobjekt zum Analysieren der Zeilendaten (direkt vom DataReader!) Und füllen Sie das Objekt.
  • machen Sie statische Methoden in den abgeleiteten Geschäftsobjekten, die die gespeicherten Prozessparameter vorbereiten (abhängig von verschiedenen Filterkriterien) und rufen Sie die generischen Datenzugriffsmethoden von der Basisklasse auf.

Ziel ist es mit der Nutzung, um am Ende wie:

List<MyObject> objects = MyObject.FindMyObject(string someParam); 

Der Vorteil für mich war, dass ich nur eine Datei zu ändern, um mit Änderungen in den Datenbank Spaltennamen, Typen zu bewältigen usw. (kleine Änderungen im Allgemeinen). Mit einigen gut durchdachten Regionen können Sie den Code so organisieren, dass es sich um separate "Ebenen" im selben Objekt handelt :). Der andere Vorteil ist, dass die Basisklasse wirklich von einem Projekt zum anderen wiederverwendet werden kann. Und der Code Bloat ist minimal (gut, verglichen mit den Vorteilen. Sie könnten auch Datasets füllen und sie an UI-Kontrollen binden: D

Die Einschränkungen - Sie am Ende mit einer Klasse pro Domain-Objekt (in der Regel pro Haupt-Datenbank-Tabelle) Und Sie können keine Objekte in bestehenden Transaktionen laden (obwohl Sie die Transaktion weitergeben könnten, wenn Sie eine haben)

Lassen Sie mich wissen, wenn Sie an weiteren Details interessiert sind - ich könnte die Antwort erweitern a Bit.

3

haben meine eigenen hinzufügen:
Return DataReader from DataLayer in Using statement

Das neue Muster ermöglicht es mich, nur zu einem Zeitpunkt, in dem Speicher einen Datensatz haben, aber einhüllt noch die Verbindung in einer schönen ‚mit‘ Aussage:

public IEnumerable<T> GetSomeData(string filter, Func<IDataRecord, T> factory) 
{ 
    string sql = "SELECT * FROM [SomeTable] WHERE SomeColumn= @Filter"; 

    using (SqlConnection cn = new SqlConnection(GetConnectionString())) 
    using (SqlCommand cmd = new SqlCommand(sql, cn)) 
    { 
     cmd.Parameters.Add("@Filter", SqlDbType.NVarChar, 255).Value = filter; 
     cn.Open(); 

     using (IDataReader rdr = cmd.ExecuteReader()) 
     { 
      while (rdr.Read()) 
      { 
       yield return factory(rdr); 
      } 
      rdr.Close(); 
     } 
    } 
} 
+0

Wo ist Ihre Ausnahme Handhabung? – msfanboy

+9

@msfanboy - wo es hingehört, auf einer höheren Ebene im Programm –

+0

Wie auch immer, das ist jetzt alt. Ich habe das Muster seitdem weiter entwickelt: http://stackoverflow.com/questions/2862428/fastest-method-for-sql-server-inserts-updates-selects/2862490#2862490 –

1

ähnlich, was ich geschrieben here

public IEnumerable<S> Get<S>(string query, Action<IDbCommand> parameterizer, 
          Func<IDataRecord, S> selector) 
{ 
    using (var conn = new T()) //your connection object 
    { 
     using (var cmd = conn.CreateCommand()) 
     { 
      if (parameterizer != null) 
       parameterizer(cmd); 
      cmd.CommandText = query; 
      cmd.Connection.ConnectionString = _connectionString; 
      cmd.Connection.Open(); 
      using (var r = cmd.ExecuteReader()) 
       while (r.Read()) 
        yield return selector(r); 
     } 
    } 
} 

ich diese einfachen Erweiterungsmethoden müssen helfen Leichtigkeit des Aufrufs:

public static void Parameterize(this IDbCommand command, string name, object value) 
{ 
    var parameter = command.CreateParameter(); 
    parameter.ParameterName = name; 
    parameter.Value = value; 
    command.Parameters.Add(parameter); 
} 

public static T To<T>(this IDataRecord dr, int index, T defaultValue = default(T), 
         Func<object, T> converter = null) 
{ 
    return dr[index].To<T>(defaultValue, converter); 
} 

static T To<T>(this object obj, T defaultValue, Func<object, T> converter) 
{ 
    if (obj.IsNull()) 
     return defaultValue; 

    return converter == null ? (T)obj : converter(obj); 
} 

public static bool IsNull<T>(this T obj) where T : class 
{ 
    return (object)obj == null || obj == DBNull.Value; 
} 

So, jetzt ich anrufen kann:

var query = Get(sql, cmd => 
{ 
    cmd.Parameterize("saved", 1); 
    cmd.Parameterize("name", "abel"); 
}, r => new User(r.To<int>(0), r.To<string>(1), r.To<DateTime?>(2), r.To<bool>(3))); 
foreach (var user in query) 
{ 

} 

Das komplett generisch ist, passt sich jedem Modell, das auf ado.net Schnittstellen entsprechen. The connection object and reader is disposed only after the collection is enumerated once.

-1

Die einfachste Lösung:

var dt=new DataTable(); 
dt.Load(myDataReader); 
list<DataRow> dr=dt.AsEnumerable().ToList(); 
+0

Das vermisst den Punkt vollständig. Es ist wahrscheinlich auch die langsamste Antwort hier, da es das gesamte Ergebnis im RAM nicht nur einmal, sondern zweimal lädt. –