zoukankan      html  css  js  c++  java
  • 由“使用存储过程”引发的一些思考(高手请进)

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

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

  • 相关阅读:
    tomcat解决 java.lang.IllegalArgumentException: Request header is too large
    mysql之表操作
    类的封装
    classmethod和staticmethod
    13-轮播实现(各种)
    12-事件委托(事件代理)
    python基础篇笔记03 迭代器、生成器、推导式、内置函数
    python基础篇笔记02 文件操作 序列解包**星号语法
    Django自定制分页器Pagination
    Ajax相关 及 解决csrf_token、Forbidden(CSRF)问题
  • 原文地址:https://www.cnblogs.com/skysoft001/p/2364073.html
Copyright © 2011-2022 走看看