当前位置:编程学习 > C#/ASP.NET >>

三层结构在表示层无法获取数据访问层DataReader的引用,如何显示关闭DataReader对象

标题:三层结构在表示层无法获取数据访问层DataReader的引用,如何显示关闭DataReader对象

问题所在:ASP.NET+MySQL数据库做的一个网站,由于表示层无法获取数据访问层DataReader的引用,因此无法显式关闭在数据访问层的DataReader对象。因此访问人数多了,链接数就多了,打开网站Repeater控件页面,就报错“连接数已达到最大限度”。

初步分析原因:在这里,做不到直接关闭数据访问层的DataReader对象。所以不能及时关闭连接释放资源,导致连接数过多。

一、通用数据访问层(DBUtility)内的数据访问助手(MySQLHelper.cs)里面有一个用来返回DataReader对象的方法。

public static MySqlDataReader ExecuteMySqlReader(string sqlStr)
{
    MySqlConnection conn = new MySqlConnection(MyConString);
    MySqlCommand cmd = new MySqlCommand(sqlStr, conn);
    MySqlDataReader dr = null;
    try
    {
        if(conn.State!=ConnectionState.Open)
            conn.Open();
        //执行CloseConnection命令时,如果关闭关联的DataReader对象,则关联的Connection对象也将关闭
        //用using(conn)会报错,因为执行完命令就会关闭连接,其它代码调用DataReader对象时,连接已经关闭。
        dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
        return dr;
    }
    catch (Exception exp)
    {
        throw new Exception(exp.Message);
    }
    finally
    {
        //dr.Close(); 这里不能关闭MySqlDataReader对象,因为关闭后,其它代码调用这个方法就会报错,提示阅读器已经关闭
        cmd.Dispose();
    }
}



二、在数据访问层(DAL)调用MySqlHelper.cs的ExecuteReader()方法。


/// <summary>
/// 传入店铺ID,返回该店铺信息
/// </summary>
/// <param name="shopID"></param>
/// <returns></returns>
public shop_basic_info GetShopInfoByID(int shopID)
{
    string sqlStr = @"select shop_basic_info.ID,shop_name,shop_url,shop_item_list,shop_seller_id,platform_id 
                    from shop_basic_info
                    WHERE ID="+shopID+"";
    shop_basic_info shopBasic = new shop_basic_info();
    MySqlDataReader dr = MySqlHelper.ExecuteReader(MySQLHelper.MyConString, sqlStr);
    while (dr.Read())
    {
        shopBasic.shop_name = dr["shop_name"].ToString();
        shopBasic.shop_url = dr["shop_url"].ToString();
        shopBasic.shop_item_list = dr["shop_item_list"].ToString();
        shopBasic.shop_seller_id = dr["shop_seller_id"].ToString();
        shopBasic.platform_id = dr["platform_id"].ToString() ;
    }
    return shopBasic;
}



三、因此我们可以看到,这些过程中,我们一直没有显式地关闭MySqlDataReader对象。所以会导致上述问题。


四、以下是摘自MSDN上的一段话:
    有些开发人员坚持认为,如果您设置CommandBehavior.CloseConnection选项,则DataReader及其相关联的连接会在DataReader完成数据读取时自动关闭。这些开发人员的看法不完全正确 —  只有当您在ASP.NET Web应用程序中使用复杂的绑定控件时,该选项才以这种方式工作。在整个DataReader结果集中循环到其行集的末尾(也就是说,当Dr.Read()返回False时)还不足以触发连接的自动关闭。不过,如果您绑定到一个复杂的绑定控件(例如,DataGrid),该控件则会关闭DataReader和连接—前提条件是您设置了CommandBehavior.CloseConnection选项。

五、网上找到的解决方案(我看了以后不明白“使用AdoHelper的GetConnection方法建立连接对象”求解释)
    DataReader需要打开的连接才能循环读取数据,所以AdoHelper不可能为你关闭这个连接而连接是在AdoHelper内部创建的,你得不到它的引用,没法自己关闭它。所以这个地方实际上是AdoHelper的一个缺陷,SqlHelper也有类似的问题。解决办法是,使用AdoHelper的GetConnection方法建立连接对象,然后使用这个连接查询,最后关闭不然只有等GC销毁连接对象时才会释放资源。


六、这个问题困扰了我很久,到现在还没有解决,请给我提供解决方案,谢谢! --------------------编程问答-------------------- 你完全可以在DataReader读取的时候,每读取一条,将数据放在List<Model>里并返回,而不是返回一个需要显示关闭的DataReader --------------------编程问答--------------------

 List<News> modelList = new List<News>();
string cmdTxt="select ```````";
News model = null;
            SqlParameter[] pars = new SqlParameter[]
            {
                new SqlParameter("@TabeName",TabeName),
                new SqlParameter("@Fields",Fields),
                new SqlParameter("@SearchWhere",SearchWhere),
                new SqlParameter("@OrderFields",OrderFields),
                new SqlParameter("@pageSize",pageSize),
                new SqlParameter("@pageIndex",pageIndex)
            };
            SqlDataReader dr = SqlHelperMain.ProcExecGetReader(cmdText, CommandType.StoredProcedure, pars);
            
                while (dr.Read())
                {
                    model = new News();
                    //model.RowNow = int.Parse(dr["Rows"].ToString());
                    model.ID = int.Parse(dr["ID"].ToString());
                    model.NewsTypeID = int.Parse(dr["NewsTypeID"].ToString());
                    model.Title = dr["Title"].ToString();
                    model.CreateTime = DateTime.Parse(dr["CreateTime"].ToString());
                    
                    modelList.Add(model);
                }
            }

            return modelList;

封装的帮助类:

 /////////////////////////////执行存储过程通用方法 【 start 】/////////////////////////////////////
        /// <summary>
        /// 执行存储过程通用方法:返回SqlDataReader对象
        /// </summary>
        /// <param name="proText">存储过程名字</param>
        /// <param name="cmdType">查询类型</param>
        /// <param name="pars">参数列表</param>
        /// <returns>返回:SqlDataReader</returns>
        public static SqlDataReader ProcExecGetReader(string cmdText,CommandType cmdType, params SqlParameter[] pars)
        {
            SqlConnection conn = null;
            try
            {
                conn=new SqlConnection(connStr);
                conn.Open();
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = conn;
                cmd.Parameters.AddRange(pars);
                cmd.CommandType = cmdType;
                cmd.CommandText = cmdText;
                return cmd.ExecuteReader(CommandBehavior.CloseConnection);//关闭关联的Connection  
            }
            catch 
            {
                conn.Close();
                return null;
            }
        }
--------------------编程问答--------------------

/// <summary>
/// 传入店铺ID,返回该店铺信息
/// </summary>
/// <param name="shopID"></param>
/// <returns></returns>
public shop_basic_info GetShopInfoByID(int shopID)
{
    string sqlStr = @"select shop_basic_info.ID,shop_name,shop_url,shop_item_list,shop_seller_id,platform_id 
                    from shop_basic_info
                    WHERE ID="+shopID+"";
    shop_basic_info shopBasic = new shop_basic_info();
    MySqlDataReader dr = MySqlHelper.ExecuteReader(MySQLHelper.MyConString, sqlStr);
    while (dr.Read())//【读取一条数据用if对吧】
    {
        shopBasic.shop_name = dr["shop_name"].ToString();
        shopBasic.shop_url = dr["shop_url"].ToString();
        shopBasic.shop_item_list = dr["shop_item_list"].ToString();
        shopBasic.shop_seller_id = dr["shop_seller_id"].ToString();
        shopBasic.platform_id = dr["platform_id"].ToString() ;
    }
    return shopBasic;
}

--------------------编程问答-------------------- DataReader这种东西就不应该出现在页面里 --------------------编程问答--------------------
引用 2 楼 zooen2011 的回复:
C# code

 List<News> modelList = new List<News>();
string cmdTxt="select ```````";
News model = null;
            SqlParameter[] pars = new SqlParameter[]
            {
                new SqlParame……

正解 --------------------编程问答-------------------- 我一般是取值之后,就把数据放到dataTable / dataSet中,在表示层调用dataTable / dataset也是一样的嘛!
数据库访问连接访问结束就并闭比较好! --------------------编程问答-------------------- 要想让它保证干净地关闭数据库连接,那么你可以把它写到一个干干净净的方法代码中。例如
public static void Execute(string sqlStr, Action<DbDataReader> onRead)
{
    using (DbConnection conn = CreateMyConnection())
    {
        var cmd = conn.CreateCommand();
        cmd.CommandText = sqlStr;
        cmd.CommandType = System.Data.CommandType.Text;
        var dr = cmd.ExecuteReader();
        while (dr.Read())
            onRead(dr);
    }
}

这个代码就保证了关闭数据库连接,放到所谓的SqlHelper中可以使用。这里将将来需要扩展的、关于读取数据进行利用的代码作为委托回调。

假设你在将来需要使用这样一种业务对象
public class 坐标点
{
    public string 名称;
    public double 经度;
    public double 纬度;
}
那么一次查询可以这样实现
var sql = "select * from abc where lon>38 and lon <39.9";
var datas = new List<坐标点>();
Action<DbDataReader> readProcess = dr =>
{
    var da = new 坐标点();
    da.名称 = (string)dr["name"];
    da.经度 = (double)dr["lon"];
    da.纬度 = (double)dr["lat"];
    datas.Add(da);
};
Execute(sql, readProcess );
//.....这里datas里边已经装好所有查询结果。
--------------------编程问答-------------------- 如果你不会利用委托、回调等基本的设计概念,那么你也许可以纠结几十种所谓“设计模式”,通过绕一个大圈子也可以搞懂如何保证在SqlHekpler中关闭数据库连接并且将扩展操作放到应用中使用的设计问题。 --------------------编程问答--------------------
引用 4 楼 danjiewu 的回复:
DataReader这种东西就不应该出现在页面里


lz的代码不是也返回MySqlDataReader 类型的东西了吗,而他说“表示层无法获取数据访问层DataReader的引用”,可见他的代码并没有在所谓表示层去直接出现调用代码。 --------------------编程问答--------------------
引用 9 楼 sp1234 的回复:
引用 4 楼 danjiewu 的回复:

DataReader这种东西就不应该出现在页面里


lz的代码不是也返回MySqlDataReader 类型的东西了吗,而他说“表示层无法获取数据访问层DataReader的引用”,可见他的代码并没有在所谓表示层去直接出现调用代码。


楼主返回的其实就是DataReader --------------------编程问答-------------------- 1楼已经说的很清楚了,下面几楼还给了一些代码

看看你自己说的:
“初步分析原因:在这里,做不到直接关闭数据访问层的DataReader对象。所以不能及时关闭连接释放资源,导致连接数过多。”

你自己已经明白了问题出在哪里,应该很容易解决了吧!

那要解决也很简单了, 像上面大家说的 不要返回DataReader啊!
直接返回DataSet,或者你就直接返回shop_basic_info

==================================================================

罗嗦几句:

如果你要返回DataReader,可以有两个选择:
1. 使用CommandBehavior.CloseConnection,使用这个参数的意思是在关闭DataReader之后,数据库会自动关闭。 但是你从来没有调用过DataReader.Close()

2. 自己手动打开数据库连接,使用完毕手动关闭数据库连接


建议改一下代码

    shop_basic_info shopBasic = null;
    MySqlDataReader dr = MySqlHelper.ExecuteReader(MySQLHelper.MyConString, sqlStr);
    if (dr.Read())
    {
       shopBasic = new shop_basic_info(dr);
    }
    dr.Close();

建议将下面这些代码:
        shopBasic.shop_name = dr["shop_name"].ToString();
        shopBasic.shop_url = dr["shop_url"].ToString();
        shopBasic.shop_item_list = dr["shop_item_list"].ToString();
        shopBasic.shop_seller_id = dr["shop_seller_id"].ToString();
        shopBasic.platform_id = dr["platform_id"].ToString() ;
都放到shop_basic_info的构造函数中。 --------------------编程问答-------------------- 我现在习惯是等到reader后就封装,关闭reader,返回封装后的结果
补充:.NET技术 ,  ASP.NET
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,