由“使用存儲過程”引發的一些思考(高手請進)


  在以前ado.net時候,我們使用存儲過程返回一個列表,可以將結果集放在DataTable中,如果我們需要將結果集放在一個強類型集合(如List<T>)中我們該怎么做呢?之前在網上看到過一種解決方法,忘記出處了,請諒解。大概思路是:在用DataReader讀取一行記錄時,將該行創建為一個對象,然后添加到列表中

  我在EF3.5中使用存儲過程,需要在edmx(領域模型中)文件中做函數導入(Function Import),並且返回值類型必須是數據庫中已存在的實體。

這樣做的缺憾(不夠靈活)如下:

  1、必須要函數導入,如果后來修改或更新實體模型,要維護該函數。(可以接受)

  2、存儲過程返回的對象必須是數據庫已存在的實體,不然無法選擇返回值類型。(目前沒找到合理方法,要在數據庫中建一個空表,無法接受,但在EF4.0中返回類型哪里可以新建ComplexType)。

  所以在項目中少許的存儲過程就用ado.net了。現有一個示例場景,從northwind的Products表中,獲取一部分數據,得到產品編號,產品名稱,單價這三列。那么存儲過程中的sql語句:select ProductID,ProductName,UnitPrice from Products。

第一個版本的實現:

1、返回的數據實體如:

View Code
 1  public class MyModel
2 {
3 public int ProductId { get; set; }
4 public string ProductName { get; set; }
5 public decimal UnitPrice { get; set; }
6
7 /// <summary>
8 /// 創建該對象
9 /// </summary>
10 /// <param name="record"></param>
11 /// <returns></returns>
12 public static MyModel Create(IDataRecord record)
13 {
14 return new MyModel()
15 {
16 ProductId = Field<int>(record, "ProductID"),
17 ProductName = Field<string>(record, "ProductName"),
18 UnitPrice = Field<decimal>(record, "UnitPrice")
19 };
20 }
21 /// <summary>
22 /// 獲取某個字段的值
23 /// </summary>
24 /// <typeparam name="T"></typeparam>
25 /// <param name="record"></param>
26 /// <param name="fieldName"></param>
27 /// <returns></returns>
28 public static T Field<T>(IDataRecord record, string fieldName)
29 {
30 T fieldValue = default(T);
31
32 if (record[fieldName] != DBNull.Value)
33 {
34 fieldValue = (T)record[fieldName];
35 }
36
37 return fieldValue;
38 }
39 }

2、在數據訪問層(DAL)中,編寫一個泛型方法。如:

View Code
 1  /// <summary>
2 /// 執行存儲過程得到集合列表
3 /// </summary>
4 /// <typeparam name="TResult"></typeparam>
5 /// <param name="procedureName">存儲過程名稱</param>
6 /// <param name="creater">創建對象的委托</param>
7 /// <param name="parameters"></param>
8 /// <returns></returns>
9 public List<TResult> ExecuteProcedureList<TResult>(string procedureName,Func<IDataRecord,TResult> creater, params SqlParameter[] parameters)
10 {
11 List<TResult> result = new List<TResult>();
12
13 //get sqlconnection string
14 string connString = (ObjectContext.Connection as EntityConnection).StoreConnection.ConnectionString;
15
16 SqlConnection conn = new SqlConnection(connString);
17 SqlCommand cmd = conn.CreateCommand();
18 cmd.CommandType = CommandType.StoredProcedure;
19 cmd.Parameters.AddRange(parameters);
20 cmd.CommandText = procedureName;
21
22 try
23 {
24 if (conn.State != ConnectionState.Open)
25 {
26 conn.Open();
27 }
28 using (DbDataReader reader = cmd.ExecuteReader())
29 {
30 while (reader.Read())
31 {
32 TResult model = creater(reader);
33 result.Add(model);
34 }
35 }
36 }
37 catch { throw; }
38 finally
39 {
40 cmd.Dispose();
41 conn.Close();
42 }
43 return result;
44 }

3、調用代碼:

List<MyModel> resultList=  bll.ExecuteProcedureList<MyModel>("proc_getlist", MyModel.Create);

上面的代碼可能存在不足,比如:

  1、MyModel中的Create方法是每個model必須自己實現(是否可以約束類型必須存在Create方法)。

  2、MyModel中的Field泛型方法是用來獲取字段的值,它的功能獨立於該Model,可以抽離出來,寫在其他地方。

  3、調用時候還必須傳遞Model中的創建對象的方法 等,是否我們可以進一步的封裝呢?

 

我的設計(第二版)是:將Field方法和Create的聲明寫在一個抽象基類中,Field方法作為工具使用static,Create作為抽象方法,讓實現者必須實現類型的創建。

 1 /// <summary>
2 /// 作為 EF中使用存儲過程返回集合 對象的基類
3 /// </summary>
4 /// <typeparam name="TResult"></typeparam>
5 public abstract class ProcedureModel
6 {
7 /// <summary>
8 /// 獲取record中字段的值,如果不存在則返回默認值
9 /// </summary>
10 /// <typeparam name="T"></typeparam>
11 /// <param name="record"></param>
12 /// <param name="fieldName"></param>
13 /// <returns></returns>
14 public static T Field<T>(IDataRecord record, string fieldName)
15 {
16 T fieldValue = default(T);
17
18 if (record[fieldName] != DBNull.Value)
19 {
20 fieldValue = (T)record[fieldName];
21 }
22
23 return fieldValue;
24 }
25
26 /// <summary>
27 /// 創建該對象
28 /// </summary>
29 /// <param name="record"></param>
30 /// <returns></returns>
31 public abstract ProcedureModel Create(IDataRecord record);
32 }
 1 public class MyModel : ProcedureModel
2 {
3 public int ProductId { get; set; }
4 public string ProductName { get; set; }
5 public decimal UnitPrice { get; set; }
6
7
8
9
10 #region 創建實體的方法
11
12 public override ProcedureModel Create(IDataRecord record)
13 {
14 return new MyModel()
15 {
16 ProductId = Field<int>(record, "ProductID"),
17 ProductName = Field<string>(record, "ProductName"),
18 UnitPrice = Field<string>(record, "UnitPrice")
19 };
20 }
21
22 #endregion
23 }

數據訪問層中:

 1  /// <summary>
2 /// 執行存儲過程返回集合
3 /// </summary>
4 /// <typeparam name="TResult">實體類型</typeparam>
5 /// <param name="procedureName">儲存過程名稱</param>
6 /// <param name="createTResult">創建實體的方法</param>
7 /// <param name="parameters">存儲過程的參數</param>
8 /// <returns></returns>
9 public List<TResult> ExecuteProcedureList<TResult>(string procedureName, params SqlParameter[] parameters)
10 where TResult : ProcedureModel,new()
11 {
12 List<TResult> result = new List<TResult>();
13
14 //get sqlconnection string
15 string connString = (ObjectContext.Connection as EntityConnection).StoreConnection.ConnectionString;
16
17 SqlConnection conn = new SqlConnection(connString);
18 SqlCommand cmd = conn.CreateCommand();
19 cmd.CommandType = CommandType.StoredProcedure;
20 cmd.Parameters.AddRange(parameters);
21 cmd.CommandText = procedureName;
22
23
24 try
25 {
26 if (conn.State != ConnectionState.Open)
27 {
28 conn.Open();
29 }
30
31 Func<IDataRecord, ProcedureModel> creater = (new TResult() as ProcedureModel).Create;
32
33 using (DbDataReader reader = cmd.ExecuteReader())
34 {
35 while (reader.Read())
36 {
37 TResult model = (TResult)creater(reader);
38 result.Add(model);
39 }
40 }
41 }
42 catch { throw; }
43 finally
44 {
45 cmd.Dispose();
46 conn.Close();
47 }
48 return result;
49 }

調用:

List<MyModel> resultList=  bll.ExecuteProcedureList<MyModel>("proc_getlist");
 

  通過以上可以看出,在第二個版本中,調用時去掉了對象創建方法的傳遞,如果其他開發者使用ExecuteProcedureList,則強制繼承ProdureModel抽象類,這樣的約束。

當然上面存儲過程沒有做分頁(數據量大的情況,會影響性能),根據需要可以自己實現。第二個版本的實現請在需要的地方合理使用。

對於第二個實現,我有一些問題想請教高手們。

  1、對於創建對象的方法Create,我覺得實現為static比較好,這樣每個實例都可以共用一個Create方法,無需每個對象都有Create方法,如何做?

  2、對於Create方法,返回值問題。現在是返回的基類型,會設計到類型轉換的問題,是否有性能影響(這里不涉及裝箱拆箱)。如果返回具體類型,該如何做?

  3、對於在DAL的ExecuteProcedureList中,如何獲取創建對象的方法問題。目前是創建一個對象,通過多肽得到子類的實現方法,如果static如何做,(反射)?

  4、總之,有沒有更好的設計呢?期待着大牛的指點,先謝謝啦

 

上面算是拋磚引玉吧,期待大家的回復和討論,我們共同進步,謝謝大家!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM