Winform开发框架之存储过程的支持--存储过程的实现和演化提炼(2)

本篇继续上篇《Winform开发框架之存储过程的支持--存储过程的实现和演化提炼(1)》来对Winform开发框架之存储过程的支持进行介绍,上篇主要介绍了SQLServer和Oracle两种数据库对常规存储过程的编写和对比,本篇主要介绍如何在C#里面,如何对这些存储过程进行调用,并获取到对应的数据类型,如输出参数,单个数据记录,多个数据记录等情况。最后在完成实现功能的基础上,对这些实现进行演化提炼,并扩展到我的WInform开发框架里面,实现功能重用、代码简化的目的。

1、数据访问接口的定义

我们整个实例是以一个客户表T_Customer为例进行讲解的,整个表的框架支持代码,可以通过代码生成工具进行快速生成,生成后包括了IDAL、Entity、DALSQL、BLL层代码,然后可以利用代码进行测试存储过程是否执行成功等功能。
数据访问层的定义,依照框架的分层模式来处理,后面我们在增加DALOracle对Oracle数据库进行支持即可。
生成后数据访问层接口,他们通过基类接口继承的方式,已经具有了常规的增删改查、分页等系列接口,但是其他业务接口还是需要自己定义的,如数据访问接口成的定义如下所示。

namespace WHC.TestProject.IDAL
{
    /// <summary>
    /// 客户信息
    /// </summary>
    public interface ICustomer : IBaseDAL<CustomerInfo>
    {
    }
}

这里面的代码很简单,没有多余的代码行,那么里面究竟发生了什么呢,其中的IBaseDAL又是什么定义呢?



其实,IBaseDAL就是定义了很多我们开发用到的基础接口,如标准的增删改查,以及衍生出来的一些其他接口,如分页查询,条件查询等接口内容。这个ICustomer就是用来定义一些除了标准接口不能实现外的业务接口。
如果我们需要实现基于存储过程的接口,我们可能就需要增加一些接口定义,如下所示。

namespace WHC.TestProject.IDAL
{
    /// <summary>
    /// 客户信息
    /// </summary>
    public interface ICustomer : IBaseDAL<CustomerInfo>
    {
        #region 使用存储过程
        /// <summary>
        /// 使用存储过程插入数据
        /// </summary>
        /// <param name="info">实体对象</param>
        /// <returns></returns>
        bool StorePorc_Insert(CustomerInfo info, DbTransaction trans = null);

        /// <summary>
        /// 使用存储过程更新数据
        /// </summary>
        /// <param name="info">实体对象</param>
        /// <returns></returns>
        bool StorePorc_Update(CustomerInfo info, DbTransaction trans = null);

        /// <summary>
        /// 使用存储过程获取所有数据
        /// </summary>
        /// <returns></returns>
        List<CustomerInfo> StorePorc_GetAll(DbTransaction trans = null);

        /// <summary>
        /// 使用存储过程获取所有数据
        /// </summary>
        /// <returns></returns>
        DataTable StorePorc_GetAllToDataTable(DbTransaction trans = null);
            
        /// <summary>
        /// 使用存储过程,根据ID获取对应记录
        /// </summary>
        /// <param name="ID"></param>
        /// <returns></returns>
        CustomerInfo StorePorc_FindByID(string ID, DbTransaction trans = null);

        /// <summary>
        /// 使用存储过程,判断记录ID是否存在
        /// </summary>
        /// <param name="ID">记录ID</param>
        /// <returns></returns>
        bool StorePorc_ExistByID(string ID, DbTransaction trans = null);

        /// <summary>
        /// 使用存储过程,根据ID删除对应记录
        /// </summary>
        /// <param name="ID">记录ID</param>
        /// <returns></returns>
        bool StorePorc_DeleteByID(string ID, DbTransaction trans = null);

        /// <summary>
        /// 获取客户的最大年龄
        /// </summary>
        /// <returns></returns>
        int StorePorc_GetMaxAge();

        #endregion
    }

对于插入、更新和删除这样的操作,我们只需要返回它是否成功就可以了,那么它的接口实现应该是如何的呢?

2、SqlServer存储过程的调用实现

由于我们的Winform开发框架底层是利用微软企业库EnterpriseLibrary来访问数据的,那么对应这个企业库的使用存储过程的方法,也就是我们的实现了。

下面的代码就是它们对应的SqlServer实现了。

public bool StorePorc_Insert(CustomerInfo info, DbTransaction trans = null)
{
    string procName = "T_Customer_Insert";

    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(procName);

    db.AddInParameter(command, "@ID", DbType.String, info.ID);
    db.AddInParameter(command, "@Name", DbType.String, info.Name);
    db.AddInParameter(command, "@Age", DbType.Int32, info.Age);
                           
    bool result = false;
    if (trans != null)
    {
        result = db.ExecuteNonQuery(command, trans) > 0;
    }
    else
    {
        result = db.ExecuteNonQuery(command) > 0;
    }
    return result;
}

public bool StorePorc_Update(CustomerInfo info, DbTransaction trans = null)
{
    string procName = "T_Customer_UpdateByID";

    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(procName);

    db.AddInParameter(command, "@ID", DbType.String, info.ID);
    db.AddInParameter(command, "@Name", DbType.String, info.Name);
    db.AddInParameter(command, "@Age", DbType.Int32, info.Age);
                           
    bool result = false;
    if (trans != null)
    {
        result = db.ExecuteNonQuery(command, trans) > 0;
    }
    else
    {
        result = db.ExecuteNonQuery(command) > 0;
    }
    return result;       
}


public bool StorePorc_DeleteByID(string ID, DbTransaction trans = null)
{
    string procName = "T_Customer_DeleteByID";

    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(procName);
    db.AddInParameter(command, "@ID", DbType.String, ID);

    bool result = false;
    if (trans != null)
    {
        result = db.ExecuteNonQuery(command, trans) > 0;
    }
    else
    {
        result = db.ExecuteNonQuery(command) > 0;
    }
    return result;  
}

对于有返回输出参数的值,我们的做法有些不同,不过最主要的还是最终获取它的输出参数值而已,代码如下所示。

public bool StorePorc_ExistByID(string ID, DbTransaction trans = null)
{
    bool result = false;
    string procName = "T_Customer_ExistByID";

    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(procName);
    db.AddInParameter(command, "@ID", DbType.String, ID);
    db.AddOutParameter(command, "@Exist", DbType.Int32, 0);//输出参数
                
    if (trans != null)
    {                
        db.ExecuteNonQuery(command, trans);
    }
    else
    {
        db.ExecuteNonQuery(command);
    }

    int iExist = 0; 
    int.TryParse(db.GetParameterValue(command, "@Exist").ToString(), out iExist);

    result = iExist > 0;
    return result;       
}


public int StorePorc_GetMaxAge()
{
    string procName = "T_Customer_MaxAge";

    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(procName);

    db.AddOutParameter(command, "@MaxAge", DbType.Int32, 0);//输出参数

    db.ExecuteNonQuery(command);

    int maxAge = 0;
    int.TryParse(db.GetParameterValue(command, "@MaxAge").ToString(), out maxAge);

    return maxAge;     
}

上面的代码,主要就是利用了AddOutParameter对输出参数的信息进行设置,输出参数的数据类型要和脚本里面的类型定义对应,它的AddOutParameter的size参数值,可以为0。

最后我们通过db.GetParameterValue(command, "@MaxAge")的方式获取它的输出参数的值,并返回即可。

最后一个例子是介绍如何通过代码调用,获得它的实体对象或者实体对象列表,以及DataTable集合对象的例子了,这个也相对不是很麻烦,参照框架里面的做法即可。

获取实体对象信息的代码如下所示。

public CustomerInfo StorePorc_FindByID(string ID, DbTransaction trans = null)
{
    string procName = "T_Customer_SelectByID";

    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(procName);
    db.AddInParameter(command, "@ID", DbType.String, ID);

    CustomerInfo entity = null;
    if (trans != null)
    {
        using (IDataReader dr = db.ExecuteReader(command, trans))
        {
            if (dr.Read())
            {
                entity = DataReaderToEntity(dr);
            }
        }
    }
    else
    {
        using (IDataReader dr = db.ExecuteReader(command))
        {
            if (dr.Read())
            {
                entity = DataReaderToEntity(dr);
            }
        }
    }
    return entity;
}

获取集合的代码如下所示。

public List<CustomerInfo> StorePorc_GetAll(DbTransaction trans = null)
{
    string procName = "T_Customer_SelectAll";

    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(procName);

    List<CustomerInfo> list = new List<CustomerInfo>();
    CustomerInfo entity = null;
    if (trans != null)
    {
        using (IDataReader dr = db.ExecuteReader(command, trans))
        {
            while (dr.Read())
            {
                entity = DataReaderToEntity(dr);
                list.Add(entity);
            }
        }
    }
    else
    {
        using (IDataReader dr = db.ExecuteReader(command))
        {
            while (dr.Read())
            {
                entity = DataReaderToEntity(dr);
                list.Add(entity);
            }
        }
    }
    return list;
}

public DataTable StorePorc_GetAllToDataTable(DbTransaction trans = null)
{
    string procName = "T_Customer_SelectAll";

    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(procName);

    DataTable dt = null;
    if (trans != null)
    {
        dt = db.ExecuteDataSet(command, trans).Tables[0];
    }
    else
    {
        dt = db.ExecuteDataSet(command).Tables[0];
    }
    return dt;
}

3、Oracle存储过程的调用实现

上面是基于SqlServer存储过程的调用,前面的一篇文章我们介绍了存储过程的Oracle定义,是增加了一个游标来进行记录行数据的处理的,不管对于单行记录,还是多行记录,都是用了游标的输出参数的,那么在客户端里面,使用EnterpriseLibrary,应该如何调用,并且不需要传入这个输出参数的呢,做法其实很类似,只是有一点差异而已。

我们先从最简单的Oracle存储过程调用案例开始,介绍如何调用插入、更新和删除操作的Oracle存储过程的调用。这里和SqlServer的类似,不同的是我们使用了p_前缀来定义参数(基于Oracle的通用脚本参数定义规则)。

public bool StorePorc_Insert(CustomerInfo info, DbTransaction trans = null)
{
    string procName = "T_Customer_Insert";

    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(procName);

    db.AddInParameter(command, "p_ID", DbType.String, info.ID);
    db.AddInParameter(command, "p_Name", DbType.String, info.Name);
    db.AddInParameter(command, "p_Age", DbType.Int32, info.Age);

    bool result = false;
    if (trans != null)
    {
        result = db.ExecuteNonQuery(command, trans) > 0;
    }
    else
    {
        result = db.ExecuteNonQuery(command) > 0;
    }
    return result;
}

public bool StorePorc_Update(CustomerInfo info, DbTransaction trans = null)
{
    string procName = "T_Customer_UpdateByID";

    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(procName);

    db.AddInParameter(command, "p_ID", DbType.String, info.ID);
    db.AddInParameter(command, "p_Name", DbType.String, info.Name);
    db.AddInParameter(command, "p_Age", DbType.Int32, info.Age);

    bool result = false;
    if (trans != null)
    {
        result = db.ExecuteNonQuery(command, trans) > 0;
    }
    else
    {
        result = db.ExecuteNonQuery(command) > 0;
    }
    return result;
}

Oracle输出外部参数的做法也和sqlServer类似,具体调用代码如下所示。

public int StorePorc_GetMaxAge()
{
    string procName = "T_Customer_MaxAge";

    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(procName);

    db.AddOutParameter(command, "p_MaxAge", DbType.Int32, 0);//输出参数

    db.ExecuteNonQuery(command);

    int maxAge = 0;
    int.TryParse(db.GetParameterValue(command, "p_MaxAge").ToString(), out maxAge);

    return maxAge;
}  

其他的也就很类似,就不再一一赘述了,基本上和SqlServer的一致,我们节省篇幅,用来看看如何调用返回记录的查询接口。下面是对应的Oracle存储过程的调用代码

public CustomerInfo StorePorc_FindByID(string ID, DbTransaction trans = null)
{
    string procName = "T_Customer_SelectByID";

    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(procName);
    db.AddInParameter(command, "p_ID", DbType.String, ID);

    CustomerInfo entity = null;
    if (trans != null)
    {
        using (IDataReader dr = db.ExecuteReader(command, trans))
        {
            if (dr.Read())
            {
                entity = DataReaderToEntity(dr);
            }
        }
    }
    else
    {
        using (IDataReader dr = db.ExecuteReader(command))
        {
            if (dr.Read())
            {
                entity = DataReaderToEntity(dr);
            }
        }
    }
    return entity;
}

返回多条记录的操作代码如下所示。

public List<CustomerInfo> StorePorc_GetAll(DbTransaction trans = null)
{
    string procName = "T_Customer_SelectAll";

    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(procName);

    List<CustomerInfo> list = new List<CustomerInfo>();
    CustomerInfo entity = null;
    if (trans != null)
    {
        using (IDataReader dr = db.ExecuteReader(command, trans))
        {
            while (dr.Read())
            {
                entity = DataReaderToEntity(dr);
                list.Add(entity);
            }
        }
    }
    else
    {
        using (IDataReader dr = db.ExecuteReader(command))
        {
            while (dr.Read())
            {
                entity = DataReaderToEntity(dr);
                list.Add(entity);
            }
        }
    }
    return list;
}

看完上面两个对记录处理的接口,我们看到,还是对我们在Oracle存储过程里面定义的输出游标参数忽略处理,我们不需要对它进行传值,它好像是透明的,呵呵。

这样它的做法就和SqlServer个各个接口实现也都差不多的了。

下面的脚本是我们之前定义的Oracle存储过程脚本,方便对比参照一下调用的函数代码。

------------------------------------
--作者:伍华聪 http://wuhuacong.cnblogs.com
--创建时间:2014年11月27日 
--功能描述:以字段ID为关键字,检索表中的数据 
------------------------------------
 Create Or Replace Procedure T_Customer_SelectByID 
 ( 
     cur_OUT OUT MyCURSOR.cur_OUT  ,
     p_ID IN T_CUSTOMER.ID%TYPE 
 ) 
 AS 
 Begin 
 OPEN cur_OUT FOR Select * from T_CUSTOMER Where ID = p_ID ; 
 End; 
 / 

4、业务逻辑层的实现

上面我们定义了数据访问接口,以及两种数据实现层,在框架里面会根据不同的数据库类型配置,然后从不同的数据库访问层构建对象的,业务逻辑层主要就是对他们的接口进行调用了,具体代码如下所示。

/// <summary>
/// 客户信息
/// </summary>
public class Customer : BaseBLL<CustomerInfo>
{
    public Customer() : base()
    {
        base.Init(this.GetType().FullName, System.Reflection.Assembly.GetExecutingAssembly().GetName().Name);
    }

    /// <summary>
    /// 根据客户名称获取客户列表
    /// </summary>
    /// <param name="name">客户名称</param>
    /// <returns></returns>
    public List<CustomerInfo> FindByName(string name)
    {
        string condition = string.Format("Name like '%{0}%' ", name);
        return baseDal.Find(condition);
    }

    #region 使用存储过程
    public bool StorePorc_Insert(CustomerInfo info, DbTransaction trans = null)
    {
        ICustomer dal = baseDal as ICustomer;
        return dal.StorePorc_Insert(info, trans);
    }

    public bool StorePorc_Update(CustomerInfo info, DbTransaction trans = null)
    {
        ICustomer dal = baseDal as ICustomer;
        return dal.StorePorc_Update(info, trans);
    }

    public List<CustomerInfo> StorePorc_GetAll(DbTransaction trans = null)
    {
        ICustomer dal = baseDal as ICustomer;
        return dal.StorePorc_GetAll(trans);
    }

    public DataTable StorePorc_GetAllToDataTable(DbTransaction trans = null)
    {
        ICustomer dal = baseDal as ICustomer;
        return dal.StorePorc_GetAllToDataTable(trans);
    }

    public CustomerInfo StorePorc_FindByID(string ID, DbTransaction trans = null)
    {
        ICustomer dal = baseDal as ICustomer;
        return dal.StorePorc_FindByID(ID, trans);
    }

    public bool StorePorc_ExistByID(string ID, DbTransaction trans = null)
    {
        ICustomer dal = baseDal as ICustomer;
        return dal.StorePorc_ExistByID(ID, trans);
    }

    public bool StorePorc_DeleteByID(string ID, DbTransaction trans = null)
    {
        ICustomer dal = baseDal as ICustomer;
        return dal.StorePorc_DeleteByID(ID, trans);
    }

    public int StorePorc_GetMaxAge()
    {
        ICustomer dal = baseDal as ICustomer;
        return dal.StorePorc_GetMaxAge();
    }
    #endregion
}

为了验证我们的实现是否能够正常处理,并顺利获取对应的对象或者集合,我们需要编写一些代码,用来对它进行测试。

测试的代码如下所示。

/// <summary>
/// 测试存储过程的插入、修改、返回实体类、返回实体类集合、返回DataTable对象、输出参数等接口
/// </summary>
private void btnTestStoreProc_Click(object sender, EventArgs e)
{
    //定义一个实体类的数据
    CustomerInfo info = new CustomerInfo();
    info.Name = "测试名称";
    info.Age = 20;

    //调用存储过程插入数据,并判断是否成功
    bool inserted = BLLFactory<Customer>.Instance.StorePorc_Insert(info);
    Debug.Assert(inserted);

    //调用存储过程,获取输出参数,获得最大年龄值
    int maxAge = BLLFactory<Customer>.Instance.StorePorc_GetMaxAge();
    Debug.Assert(maxAge > 0);

    //调用存储过程,修改客户名称
    info.Name = "修改名称";
    bool updated = BLLFactory<Customer>.Instance.StorePorc_Update(info);

    //调用存储过程,获取最新的实体类对象,并对比是否修改成功
    CustomerInfo newInfo = BLLFactory<Customer>.Instance.StorePorc_FindByID(info.ID);
    Debug.Assert(newInfo != null);
    Debug.Assert(newInfo.Name == info.Name);

    //调用存储过程,获取输出参数,判断指定ID记录是否存在
    bool exist = BLLFactory<Customer>.Instance.StorePorc_ExistByID(info.ID);
    Debug.Assert(exist);

    //调用存储过程,获取全部实体列表集合,判断实体类列表是否正确
    List<CustomerInfo> list = BLLFactory<Customer>.Instance.StorePorc_GetAll();
    Debug.Assert(list.Count > 0);

    //调用存储过程,获取DataTable对象,判断集合不为空
    DataTable dt = BLLFactory<Customer>.Instance.StorePorc_GetAllToDataTable();
    Debug.Assert(dt.Rows.Count > 0);

    //调用存储过程,执行删除操作,并判断是否成功了
    bool deleted = BLLFactory<Customer>.Instance.StorePorc_DeleteByID(info.ID);
    Debug.Assert(deleted);

    string result = "全部操作完成";
    Console.WriteLine(result);
    MessageUtil.ShowTips(result);
}

5、具体测试和验证

为了对他们进行测试,我们需要分别对SqlServer和Oracle进行测试,然后才能确认我们的实现是正确的。
分别在SQLServer和Oracle上运行存储过程脚本,创建对应的数据库脚本,如下所示。




测试Winform小程序,会得到成功的标志,标识所有的断言全部通过。


6、框架基类的演化提炼

本来写到上面小节,应该就可以告一段落了,因为功能也已经完成了,而且还是支持了两种不同的数据库,说明我们的实现和原先的想法都是正确的。
但是,我从来不喜欢臃肿的代码,我们留心回头看看前面的代码,两种不同数据库的实现很多是相似的,即使对于同一个数据库(如SQLServer)的存储过程接口实现,他们还是有很多优化的地方,代码依旧不够精简和优化,本小节就是专门针对这些进行提炼和优化的。
前面的框架介绍文章,我们可以了解到,数据访问接口实现层和接口定义层一样,都有一个基类,如基于SqlServer实现的基类为BaseDALSQL,这个基于SqlServer的数据访问基类,它也是继承自一个超级基类(大多数的实现在这里)AbstractBaseDAL。他们之间的继承关系如下所示,最终我们把提炼好的内容,放到这个AbstractBaseDAL就可以了,这样各个子类都可以进行调用,实现存储过程的处理。



对于存储过程的实现,我们分析一下各个接口,可以看到,输入参数是可选的,因为有些接口不需要输出参数;输出参数也是可选的,有些接口也不需要输出参数,返回的记录类型主要有bool类型,实体类型,实体集合类型,DataTable类型这几种,当然虽然有年龄接口的整形,但是这个是通过输出参数来获得的。
我们于是可以定义一个类似这样的通用接口参数集合,用来处理需要返回是否成功获取带有输出参数的,事务对象的接口,如下所示。

/// <summary>
/// 执行存储过程,如果影响记录数,返回True,否则为False,修改并输出外部参数outParameters(如果有)。
/// </summary>
/// <param name="storeProcName">存储过程名称</param>
/// <param name="inParameters">输入参数,可为空</param>
/// <param name="outParameters">输出参数,可为空</param>
/// <param name="trans">事务对象,可为空</param>
/// <returns>如果影响记录数,返回True,否则为False</returns>
public bool StorePorcExecute(string storeProcName, Hashtable inParameters = null, Hashtable outParameters = null, DbTransaction trans = null)

它的实现基本上就是分为了几部分,第一部分是传入参数值(包括输入参数、输出参数的值),第二部是执行存储过程,三部分是获得输出参数并修改值即可。

具体的实现代码如下所示。

/// <summary>
/// 执行存储过程,如果影响记录数,返回True,否则为False,修改并输出外部参数outParameters(如果有)。
/// </summary>
/// <param name="storeProcName">存储过程名称</param>
/// <param name="inParameters">输入参数,可为空</param>
/// <param name="outParameters">输出参数,可为空</param>
/// <param name="trans">事务对象,可为空</param>
/// <returns>如果影响记录数,返回True,否则为False</returns>
public bool StorePorcExecute(string storeProcName, Hashtable inParameters = null, Hashtable outParameters = null, DbTransaction trans = null)
{
    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(storeProcName);
    //参数传入
    SetStoreParameters(db, command, inParameters, outParameters);

    //获取执行结果
    bool result = false;
    if (trans != null)
    {
        result = db.ExecuteNonQuery(command, trans) > 0;
    }
    else
    {
        result = db.ExecuteNonQuery(command) > 0;
    }

    //获取输出参数的值
    EditOutParameters(db, command, outParameters);

    return result;
}

上面两部分红色哪里,因为他们在很多其他函数里面也通用,所以我就抽离作为一个私有函数了,就是传入参数,和传出结果的两部分。

由于输入输出参数都是可选的,因为我们不确定它是否存在值,所以我们分别对它进行了一定的处理,具体两个函数的代码如下所示。

/// <summary>
/// 传入输入参数和输出参数到Database和DbCommand对象。
/// </summary>
/// <param name="db">Database对象</param>
/// <param name="command">DbCommand对象</param>
/// <param name="inParameters">输入参数的哈希表</param>
/// <param name="outParameters">输出参数的哈希表</param>
private void SetStoreParameters(Database db, DbCommand command, Hashtable inParameters = null, Hashtable outParameters = null)
{
    #region 参数传入
    //传入输入参数
    if (inParameters != null)
    {
        foreach (string param in inParameters.Keys)
        {
            object value = inParameters[param];
            db.AddInParameter(command, param, TypeToDbType(value.GetType()), value);
        }
    }

    //传入输出参数
    if (outParameters != null)
    {
        foreach (string param in outParameters.Keys)
        {
            object value = outParameters[param];
            db.AddOutParameter(command, param, TypeToDbType(value.GetType()), 0);//size统一设置为0
        }
    }
    #endregion
}
/// <summary>
/// 执行存储过程后,获取需要输出的参数值,修改存储在哈希表里
/// </summary>
/// <param name="db">Database对象</param>
/// <param name="command">DbCommand对象</param>
/// <param name="outParameters">输出参数的哈希表</param>
private void EditOutParameters(Database db, DbCommand command, Hashtable outParameters = null)
{
    #region 获取输出参数的值
    if (outParameters != null)
    {
        ArrayList keys = new ArrayList(outParameters.Keys);//使用临时集合对象,避免迭代错误
        foreach (string param in keys)
        {
            object retValue = db.GetParameterValue(command, param);

            object value = outParameters[param];
            outParameters[param] = Convert.ChangeType(retValue, value.GetType());
        }
    }
    #endregion
}

这样我们就完成了一个普通存储过程该接口的通用处理了,但是我们知道,还有返回列表对象,列表集合,DataTable对象的几种不同方式,我们也应该要对他们进行一定的封装处理,已达到在子类能够很好使用的目的。

下面我把整个对这几部分封装的代码进行公布,它们的封装的代码如下所示(记得是放在超级抽象类上AbstractBaseDAL即可。

#region 存储过程执行通用方法

/// <summary>
/// 执行存储过程,如果影响记录数,返回True,否则为False,修改并输出外部参数outParameters(如果有)。
/// </summary>
/// <param name="storeProcName">存储过程名称</param>
/// <param name="inParameters">输入参数,可为空</param>
/// <param name="outParameters">输出参数,可为空</param>
/// <param name="trans">事务对象,可为空</param>
/// <returns>如果影响记录数,返回True,否则为False</returns>
public bool StorePorcExecute(string storeProcName, Hashtable inParameters = null, Hashtable outParameters = null, DbTransaction trans = null)
{
    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(storeProcName);
    //参数传入
    SetStoreParameters(db, command, inParameters, outParameters);

    //获取执行结果
    bool result = false;
    if (trans != null)
    {
        result = db.ExecuteNonQuery(command, trans) > 0;
    }
    else
    {
        result = db.ExecuteNonQuery(command) > 0;
    }

    //获取输出参数的值
    EditOutParameters(db, command, outParameters);

    return result;
}

/// <summary>
/// 执行存储过程,返回实体列表集合,修改并输出外部参数outParameters(如果有)。
/// </summary>
/// <param name="storeProcName">存储过程名称</param>
/// <param name="inParameters">输入参数,可为空</param>
/// <param name="outParameters">输出参数,可为空</param>
/// <param name="trans">事务对象,可为空</param>
/// <returns>返回实体列表集合</returns>
public List<T> StorePorcToList(string storeProcName, Hashtable inParameters = null, Hashtable outParameters = null, DbTransaction trans = null)
{
    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(storeProcName);
    //参数传入
    SetStoreParameters(db, command, inParameters, outParameters);

    #region 获取执行结果

    List<T> result = new List<T>();
    T entity = null;
    if (trans != null)
    {
        using (IDataReader dr = db.ExecuteReader(command, trans))
        {
            while (dr.Read())
            {
                entity = DataReaderToEntity(dr);
                result.Add(entity);
            }
        }
    }
    else
    {
        using (IDataReader dr = db.ExecuteReader(command))
        {
            while (dr.Read())
            {
                entity = DataReaderToEntity(dr);
                result.Add(entity);
            }
        }
    }
    #endregion

    //获取输出参数的值
    EditOutParameters(db, command, outParameters);

    return result;
}

/// <summary>
/// 执行存储过程,返回DataTable集合,修改并输出外部参数outParameters(如果有)。
/// </summary>
/// <param name="storeProcName">存储过程名称</param>
/// <param name="inParameters">输入参数,可为空</param>
/// <param name="outParameters">输出参数,可为空</param>
/// <param name="trans">事务对象,可为空</param>
/// <returns>返回DataTable集合</returns>
public DataTable StorePorcToDataTable(string storeProcName, Hashtable inParameters = null, Hashtable outParameters = null, DbTransaction trans = null)
{
    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(storeProcName);
    //参数传入
    SetStoreParameters(db, command, inParameters, outParameters);

    #region 获取执行结果

    DataTable result = null;
    if (trans != null)
    {
        result = db.ExecuteDataSet(command, trans).Tables[0];
    }
    else
    {
        result = db.ExecuteDataSet(command).Tables[0];
    }

    if (result != null)
    {
        result.TableName = "tableName";//增加一个表名称,防止WCF方式因为TableName为空出错
    }
    #endregion

    //获取输出参数的值
    EditOutParameters(db, command, outParameters);

    return result;
}

/// <summary>
/// 执行存储过程,返回实体对象,修改并输出外部参数outParameters(如果有)。
/// </summary>
/// <param name="storeProcName">存储过程名称</param>
/// <param name="inParameters">输入参数,可为空</param>
/// <param name="outParameters">输出参数,可为空</param>
/// <param name="trans">事务对象,可为空</param>
/// <returns>返回实体对象</returns>
public T StorePorcToEntity(string storeProcName, Hashtable inParameters = null, Hashtable outParameters = null, DbTransaction trans = null)
{
    Database db = CreateDatabase();
    DbCommand command = db.GetStoredProcCommand(storeProcName);

    //参数传入
    SetStoreParameters(db, command, inParameters, outParameters);

    #region 获取执行结果

    T result = null;
    if (trans != null)
    {
        using (IDataReader dr = db.ExecuteReader(command, trans))
        {
            if (dr.Read())
            {
                result = DataReaderToEntity(dr);
            }
        }
    }
    else
    {
        using (IDataReader dr = db.ExecuteReader(command))
        {
            if (dr.Read())
            {
                result = DataReaderToEntity(dr);
            }
        }
    }
    #endregion

    //获取输出参数的值
    EditOutParameters(db, command, outParameters);

    return result;
}

/// <summary>
/// 传入输入参数和输出参数到Database和DbCommand对象。
/// </summary>
/// <param name="db">Database对象</param>
/// <param name="command">DbCommand对象</param>
/// <param name="inParameters">输入参数的哈希表</param>
/// <param name="outParameters">输出参数的哈希表</param>
private void SetStoreParameters(Database db, DbCommand command, Hashtable inParameters = null, Hashtable outParameters = null)
{
    #region 参数传入
    //传入输入参数
    if (inParameters != null)
    {
        foreach (string param in inParameters.Keys)
        {
            object value = inParameters[param];
            db.AddInParameter(command, param, TypeToDbType(value.GetType()), value);
        }
    }

    //传入输出参数
    if (outParameters != null)
    {
        foreach (string param in outParameters.Keys)
        {
            object value = outParameters[param];
            db.AddOutParameter(command, param, TypeToDbType(value.GetType()), 0);//size统一设置为0
        }
    }
    #endregion
}
/// <summary>
/// 执行存储过程后,获取需要输出的参数值,修改存储在哈希表里
/// </summary>
/// <param name="db">Database对象</param>
/// <param name="command">DbCommand对象</param>
/// <param name="outParameters">输出参数的哈希表</param>
private void EditOutParameters(Database db, DbCommand command, Hashtable outParameters = null)
{
    #region 获取输出参数的值
    if (outParameters != null)
    {
        ArrayList keys = new ArrayList(outParameters.Keys);//使用临时集合对象,避免迭代错误
        foreach (string param in keys)
        {
            object retValue = db.GetParameterValue(command, param);

            object value = outParameters[param];
            outParameters[param] = Convert.ChangeType(retValue, value.GetType());
        }
    }
    #endregion
}
#endregion

封装好这些超级基类后,我们在数据访问层里面,就可以很好地简化对存储过程的调用了,而且他们的做法都很类似,我们可以对比一下,它们调用存储过程的实现真正简化了很多。

例如对于SqlServer数据访问层,使用超级基类的接口,我们简化代码如下所示。

public bool StorePorc_Insert(CustomerInfo info, DbTransaction trans = null)
{
    Hashtable inParameters = new Hashtable();
    inParameters.Add("ID", info.ID);
    inParameters.Add("Name", info.Name);
    inParameters.Add("Age", info.Age);

    return StorePorcExecute("T_Customer_Insert", inParameters, null, trans);
}

public bool StorePorc_Update(CustomerInfo info, DbTransaction trans = null)
{
    Hashtable inParameters = new Hashtable();
    inParameters.Add("ID", info.ID);
    inParameters.Add("Name", info.Name);
    inParameters.Add("Age", info.Age);

    return StorePorcExecute("T_Customer_UpdateByID", inParameters, null, trans);
}

public List<CustomerInfo> StorePorc_GetAll(DbTransaction trans = null)
{
    return StorePorcToList("T_Customer_SelectAll", null, null, trans);
}
public DataTable StorePorc_GetAllToDataTable(DbTransaction trans = null)
{
    return StorePorcToDataTable("T_Customer_SelectAll", null, null, trans);
}
public CustomerInfo StorePorc_FindByID(string ID, DbTransaction trans = null)
{
    Hashtable inParameters = new Hashtable();
    inParameters.Add("ID", ID);

    return StorePorcToEntity("T_Customer_SelectByID", inParameters, null, trans);
}
public bool StorePorc_ExistByID(string ID, DbTransaction trans = null)
{
    Hashtable inParameters = new Hashtable();
    inParameters.Add("ID", ID);

    Hashtable outParameters = new Hashtable();
    outParameters.Add("Exist", 0);

    StorePorcExecute("T_Customer_ExistByID", inParameters, outParameters, trans);
    int exist = (int)outParameters["Exist"];
    return exist > 0;
}

public bool StorePorc_DeleteByID(string ID, DbTransaction trans = null)
{
    Hashtable inParameters = new Hashtable();
    inParameters.Add("ID", ID);

    return StorePorcExecute("T_Customer_DeleteByID", inParameters, null, trans);
}

public int StorePorc_GetMaxAge()
{
    Hashtable outParameters = new Hashtable();
    outParameters.Add("MaxAge", 0);

    StorePorcExecute("T_Customer_MaxAge", null, outParameters, null);
    int MaxAge = (int)outParameters["MaxAge"];
    return MaxAge;
}

对于Oracle数据访问层的实现来说,它的接口实现一样简单,只是参数命名有所不同而已。

public bool StorePorc_Insert(CustomerInfo info, DbTransaction trans = null)
{
    Hashtable inParameters = new Hashtable();
    inParameters.Add("p_ID", info.ID);
    inParameters.Add("p_Name", info.Name);
    inParameters.Add("p_Age", info.Age);

    return StorePorcExecute("T_Customer_Insert", inParameters, null, trans);
}
public bool StorePorc_Update(CustomerInfo info, DbTransaction trans = null)
{
    Hashtable inParameters = new Hashtable();
    inParameters.Add("p_ID", info.ID);
    inParameters.Add("p_Name", info.Name);
    inParameters.Add("p_Age", info.Age);

    return StorePorcExecute("T_Customer_UpdateByID", inParameters, null, trans);
}
public List<CustomerInfo> StorePorc_GetAll(DbTransaction trans = null)
{
    return StorePorcToList("T_Customer_SelectAll", null, null, trans);
}
public DataTable StorePorc_GetAllToDataTable(DbTransaction trans = null)
{
    return StorePorcToDataTable("T_Customer_SelectAll", null, null, trans);
}
public CustomerInfo StorePorc_FindByID(string ID, DbTransaction trans = null)
{
    Hashtable inParameters = new Hashtable();
    inParameters.Add("p_ID", ID);

    return StorePorcToEntity("T_Customer_SelectByID", inParameters, null, trans);
}
public bool StorePorc_ExistByID(string ID, DbTransaction trans = null)
{
    Hashtable inParameters = new Hashtable();
    inParameters.Add("p_ID", ID);

    Hashtable outParameters = new Hashtable();
    outParameters.Add("p_Exist", 0);

    StorePorcExecute("T_Customer_ExistByID", inParameters, outParameters, trans);
    int exist = (int)outParameters["p_Exist"];
    return exist > 0;
}

public bool StorePorc_DeleteByID(string ID, DbTransaction trans = null)
{
    Hashtable inParameters = new Hashtable();
    inParameters.Add("p_ID", ID);

    return StorePorcExecute("T_Customer_DeleteByID", inParameters, null, trans);
}

public int StorePorc_GetMaxAge()
{
    Hashtable outParameters = new Hashtable();
    outParameters.Add("p_MaxAge", 0);

    StorePorcExecute("T_Customer_MaxAge", null, outParameters, null);
    int MaxAge = (int)outParameters["p_MaxAge"];
    return MaxAge;
}

以上就是我针对《Winform开发框架之存储过程的支持--存储过程的实现和演化提炼》这个主题进行的介绍和分析,希望对大家有所帮助,也希望结合我的框架,迅速开发各种不同的项目。

文章内容有点长,感谢您的耐心阅读和支持。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容