Search code examples
c#asp.net

Return datareader from method


I have the following method

    public static SqlDataReader MenuDataReader(string url)
    {
        using (SqlConnection con = new SqlConnection(connectionString))
        {
            using (SqlCommand cmd = new SqlCommand("spR_GetChildMenus", con))
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.AddWithValue("@PageUrl", url);
                cmd.Parameters.AddWithValue("@MenuId", ParameterDirection.Output);
                cmd.Parameters.AddWithValue("@ParentId", ParameterDirection.Output);
                cmd.Parameters.AddWithValue("@TitleText", ParameterDirection.Output);
                cmd.Parameters.AddWithValue("@ExternalUrl", ParameterDirection.Output);
                cmd.Parameters.AddWithValue("@FullUrl", ParameterDirection.Output);
                cmd.Parameters.AddWithValue("@ChildCount", ParameterDirection.Output);
                con.Open();
                SqlDataReader reader = cmd.ExecuteReader();
                if (reader.HasRows)
                {
                    //return reader;

                    while (reader.Read())
                    {
                        return reader;
                    }

                }
            }
        }
        return null;
    }

which im calling like this

        SqlDataReader reader = MenuDataReader(url);
        if (reader.HasRows)
        {
            while (reader.Read())
            { }}

however im getting the error message

Invalid attempt to call HasRows when reader is closed.

can anyone help me out

thanks


Solution

  • Do you really need the reader, or do you just need some way to iterate over the rows inside it? I suggest an iterator block. You can iterate over your rows inside the source method, and yield each row in turn to the caller.

    There is a twist with this technique: because you're yielding the same object with each iteration, there are cases where this can cause a problem, and so you're best off also asking for a delegate to copy the contents of the row somewhere. I also like to abstract this to a generic method that can be used for any query, and use the same delegate technique to handle parameter data, like so:

    private IEnumerable<T> GetRows<T>(string sql, Action<SqlParameterCollection> addParameters, Func<IDataRecord, T> copyRow)
    {
         using (var cn = new SqlConnection("Connection string here"))
         using (var cmd = new SqlCommand(sql, cn)
         {
             cmd.CommandType = CommandType.StoredProcedure;
             addParameters(cmd.Parameters);
             cn.Open();
             using (var rdr = cmd.ExecuteReader())
             {
                 while (rdr.Read())
                 {
                     yield return copyRow(rdr);
                 }
                 rdr.Close();
             }
         }
    }
    
    public IEnumerable<MenuItem> GetChildMenus(string url)
    {
         return GetRows<MenuItem>("spR_GetChildMenus", p =>
         {
             //these lines are copied from your question, but they're almost certainly wrong
             p.AddWithValue("@PageUrl", url);
             p.AddWithValue("@MenuId", ParameterDirection.Output);
             p.AddWithValue("@ParentId", ParameterDirection.Output);
             p.AddWithValue("@TitleText", ParameterDirection.Output);
             p.AddWithValue("@ExternalUrl", ParameterDirection.Output);
             p.AddWithValue("@FullUrl", ParameterDirection.Output);
             p.AddWithValue("@ChildCount", ParameterDirection.Output);
         }, r =>
         {
             return new MenuItem( ... );
         }
     }