标题:三层结构在表示层无法获取数据访问层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销毁连接对象时才会释放资源。
六、这个问题困扰了我很久,到现在还没有解决,请给我提供解决方案,谢谢!

解决方案 »

  1.   

    你完全可以在DataReader读取的时候,每读取一条,将数据放在List<Model>里并返回,而不是返回一个需要显示关闭的DataReader
      

  2.   


     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;
                }
            }
      

  3.   


    /// <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;
    }
      

  4.   

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

  5.   

    我一般是取值之后,就把数据放到dataTable / dataSet中,在表示层调用dataTable / dataset也是一样的嘛!
    数据库访问连接访问结束就并闭比较好!
      

  6.   

    要想让它保证干净地关闭数据库连接,那么你可以把它写到一个干干净净的方法代码中。例如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里边已经装好所有查询结果。
      

  7.   

    如果你不会利用委托、回调等基本的设计概念,那么你也许可以纠结几十种所谓“设计模式”,通过绕一个大圈子也可以搞懂如何保证在SqlHekpler中关闭数据库连接并且将扩展操作放到应用中使用的设计问题。
      

  8.   


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

  9.   


    楼主返回的其实就是DataReader
      

  10.   

    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的构造函数中。
      

  11.   

    我现在习惯是等到reader后就封装,关闭reader,返回封装后的结果