由“使用存储过程”引发的一些思考(高手请进)

系统 1738 0

  在以前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、总之,有没有更好的设计呢?期待着大牛的指点,先谢谢啦

 

上面算是抛砖引玉吧,期待大家的回复和讨论,我们共同进步,谢谢大家!

由“使用存储过程”引发的一些思考(高手请进)


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论