2009-01-12 64 views
8

最近我發現自己寫數據訪問層的選擇方法,其中的代碼全部採用一般形式:如何提高數據訪問層選擇方法模式

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

或者這樣:

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

這些方法將由業務層代碼調用,然後將基本DataTable或DataRecord轉換爲表示層可以使用的強類型業務對象。

因爲我反覆使用類似的代碼,我想確保這個代碼是最好的。那麼如何改進呢?而且,是否值得嘗試將通用代碼從此移出到它自己的方法。如果是這樣,該方法看起來像什麼(特別是關於傳入一個SqlParameter集合)?

+0

你做到這一點,就像我做的 - 雖然我真的很喜歡你怎麼堆的雙using語句,避免深度嵌套。做得好! 我感興趣的一件事是如何邏輯/ phyapan設置你的DAL與你的BIZ - 你把它放在它自己的項目?或者作爲一個命名空間?或者是什麼? – dfasdljkhfaskldjhfasklhf 2009-01-12 16:50:33

+0

如果沒有參數,您也可以堆棧數據記錄器。 DAL在它自己的程序集中,DAL + BL將共享一個共同的父命名空間。 – 2009-01-12 16:53:29

+0

是的,我現在怎麼做。業務對象和DAL對象之間的映射是什麼?即你去1:1?我看到有趣的一件事是將DAL拉入業務對象並使用CodeSmith生成全部內容。非常有趣 – dfasdljkhfaskldjhfasklhf 2009-01-12 16:59:31

回答

2

一種模式是這樣的,只要客戶端代碼中有云:

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

我一般都連接可選的,我會在某種程度上代碼來的ConnectionStrings配置部分把它或把它作爲實際的連接字符串。這讓我可以在一個關閉場景中重用dal,並且在我使用對象構造屬性存儲連接字符串時,這部分是COM +時代的一部分。

我喜歡這個,因爲它很容易閱讀和隱藏我的所有ADO代碼。

1

有很多方法來實現DBAL,在我看來你是在正確的道路上。在您的實施中需要考慮的幾點:

  • 您正在使用類似工廠的方法創建SqlConnection,這是一個小問題,但您可以對SqlCommand執行相同的操作。
  • 參數長度是可選的,所以您實際上可以將其保留在Parameter.Add調用之外。
  • 也可以創建添加參數的方法,下面的代碼示例。

使用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

首先添加參數,我想你使用ORM與滾動自己已經考慮。我不會進入這一個。

我在編寫自己的數據訪問代碼的想法:

  • 隨着時間的推移,我發現它更容易不具有獨立的DAL/BL的對象,而是他們到達後,後來合併成一個單一的對象(一段時間這個結論我發現它是一個非常知名的模式 - 即ActiveRecord)。它可能看起來不錯並且分離出來以具有單獨的DAL程序集,但是維護成本的開銷會加起來。每次添加新功能時,都必須創建更多代碼/修改更多類。根據我的經驗,維護應用程序的團隊通常比創建該應用程序的原始開發團隊少,他們會討厭所需的額外工作。
  • 對於大型團隊來說,分離DAL可能是有意義的(並讓其他團隊在其上工作,但是這對於代碼膨脹是一個很好的誘因。使用結果DataTable?迭代行,創建類型化對象並從行中獲取數據?如果答案是肯定的,可以考慮爲了在DAL和BL之間移動數據而創建的額外DataTable。爲什麼不直接從它DataReader?
  • 關於這個示例:如果你返回一個無類型的DataTable,那麼我想你必須在調用代碼中使用列名(SP調用返回的結果集),這意味着如果我有要改變數據庫中的某些內容,可能會影響到兩個圖層。

我的建議(我嘗試了兩種方法 - 建議是我想出的最新工作方法 - 它隨着時間的推移而發展)。

  • 爲您的類型化業務對象創建基類。
  • 在基類中保留對象狀態(新建,修改等)
  • 將主要數據訪問方法放在此類中,作爲靜態方法。稍加努力(提示:泛型方法+ Activator.CreateInstance),您可以爲讀取器中返回的每行創建一個業務對象。
  • 在業務對象中創建抽象方法來解析行數據(直接來自DataReader!)並填充對象。
  • 在派生的業務對象中創建靜態方法,以準備存儲的proc參數(取決於各種過濾條件)並從基類調用通用數據訪問方法。

目的是使用諸如落得:

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

對我的好處是,我只需要改變一個文件,以應付在數據庫中列名的更改,類型等等(一般的小變化)。通過一些精心設計的區域,您可以組織代碼,使它們在同一個對象中是分離的「圖層」:)。另一個好處是基類真的可以從一個項目重用到另一個項目。而且代碼膨脹很小(好處是,與優點相比,您可以填充數據集並將它們綁定到UI控件上:D

侷限性 - 最終每個域對象有一個類(通常是每個主數據庫表)並且你無法在現有交易中加載對象(雖然你可以考慮通過交易,如果你有)。

讓我知道如果你對更多細節感興趣 - 我可以擴大答案a位。

3

不得不加上我自己的:
Return DataReader from DataLayer in Using statement

的新格局使我只在內存中的一個記錄的時間,但仍包圍在一個不錯的「使用」語句的連接:

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(); 
     } 
    } 
} 
1

什麼我張貼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); 
     } 
    } 
} 

我有這些簡單的擴展方法來幫助類似緩解呼籲的:

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

所以,現在我可以打電話:

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) 
{ 

} 

這是完全通用的,適合應該符合ado.net接口的任何模型。 The connection object and reader is disposed only after the collection is enumerated once.

-1

最簡單的解決方案:

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