SqlDataReader 与数据集

SqlDataReader vs DataSet

我试图在用户登录网站时获取用户信息,当我使用 DataSet 时成功,但如果我想使用 SqlDataReader,则错误提示:Invalid attempt to read when reader is closed。我搜索了为什么会这样,我发现一篇文章说

SqlDataReader requires connection remains open in order to get the data from the server, while DataSet does not need requires connection remains open.

我的问题是:我想知道如何同时使用 SqlDataReader?这样当我想从数据库中获取数据时,我就不必一直依赖 DataSet 了。

当我尝试使用 SqlDataReader 更改读取数据函数的结构时出现问题,以便它可以随时重新使用。

代码如下:

数据库管理器class:

public SqlDataReader GetInformationDataReader(string procName, SqlParameter[] parameters)
        {
            SqlDataReader reader = null;
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                using (SqlCommand cmd = new SqlCommand(procName, conn))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    if (parameters != null)
                    {
                        foreach(SqlParameter parameter in parameters)
                        {
                            cmd.Parameters.Add(parameter);
                        }
                    }
                    reader = cmd.ExecuteReader();
                }
            }
            return reader;
        }

网络管理器class:

public ModelContexts.InformationContext GetInformation(string username)
        {
            SqlDataReader reader = null;
            ModelContexts.InformationContext context = new ModelContexts.InformationContext();
            SqlParameter[] parameters =
            {
                new SqlParameter("@Username", SqlDbType.NVarChar, 50)
            };
            parameters[0].Value = username;
            try
            {
                reader = DatabaseManager.Instance.GetInformationDataReader("GetInformation", parameters);
                while(reader.Read())
                {
                    context.FirstName = reader["FirstName"].ToString();
                    context.LastName = reader["LastName"].ToString();
                    context.Email = reader["Email"].ToString();
                }
            }
            catch(Exception ex)
            {
                throw new ArgumentException(ex.Message);
            }
            return context;
        }

控制器:

public ActionResult MainMenu(ModelContexts.InformationContext context, string firstName, string lastName, string username, string email)
        {
            context = WebManager.Instance.GetInformation(User.Identity.Name);
            firstName = context.FirstName;
            lastName = context.LastName;
            username = User.Identity.Name;
            email = context.Email;
            return View(context);
        }

模型包含字符串 return 值 getter 和 setter(名字、姓氏和电子邮件)。

视图包含 html 标签并对模型中的名字、姓氏和电子邮件进行编码。

感谢您的回答。

谢谢。

您已将 SqlConnection 对象包装在 using 子句中,因此在它的末尾调用 SqlConnect.Dispose,关闭连接。无论呼叫者正在使用您的 SqlDataReader 不再有打开的连接,因此您收到错误消息。

while DataSet does not need requires connection remains open.

这不完全正确。 DataSet 只是一个对象,通常在被 SqlDataAdapter(class 的 Fill() 方法)调用时填充。 SqlDataAdapter 处理 SqlConnection 的打开和关闭,这很可能就是该评论说明的原因。但处理该问题的是不同的 class,而不是 DataSet 本身。将 DataSet 视为保存 SqlCommand.

结果集的对象

回答您的评论...

So, shouldn't I use using keyword for this matter? In all of the Sql keyword?

我也不会采用这种方法。使用该模型,您可能很容易遇到连接泄漏错误,运行 排除池连接可能是一件不太有趣的事情。

通常最好先使用数据,然后再使用 close/dispose 连接。有一句话,"open late, close early"。这就是通常 您想要处理此问题的方式。对于您正在处理的这个问题,我不会尝试在 class 方法之间传递 SqlDataReader 对象。解决方法(保持连接打开)很容易出错。

另一个虽然过程,回到我们提到的事情,不要使用 SqlDataReader。循环读取每一行没有任何好处。根据您的结果集,只需填写 DataSet(或通常更合适的 DataTable)和 return 或者 Data[Set | Table] 或者更好的对象代表它所属的数据。

这里有一种方法可以用来保持代码非常干净,允许您在连接仍处于打开状态时从 SqlDataReader 中读取。它利用了通过的代表。希望代码是可以理解的。您可以对其进行调整以满足您的特定需求,但希望它能说明您可以使用的另一种选择。

public void GetInformationDataReader(string procName, SqlParameter[] parameters, Action<SqlDataReader> processRow)
{
    SqlDataReader reader = null;
    using (SqlConnection conn = new SqlConnection(connectionString))
    {
        conn.Open();
        using (SqlCommand cmd = new SqlCommand(procName, conn))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            if (parameters != null)
            {
                foreach(SqlParameter parameter in parameters)
                {
                    cmd.Parameters.Add(parameter);
                }
            }
            using (SqlDataReader dataReader = cmd.ExecuteReader())
            {
                while (dataReader.Read())
                {
                    // call delegate here.
                    processRow(dataReader);
                }
            }
        }
    }
    return reader;
}

public ModelContexts.InformationContext GetInformation(string username)
{
    SqlDataReader reader = null;
    ModelContexts.InformationContext context = new ModelContexts.InformationContext();
    SqlParameter[] parameters =
    {
        new SqlParameter("@Username", SqlDbType.NVarChar, 50)
    };
    parameters[0].Value = username;
    try
    {
        // Instead of returning a reader, pass in a delegate that will perform the work
        // on the data reader at the right time, and while the connection is still open.
        DatabaseManager.Instance.GetInformationDataReader(
            "GetInformation",
            parameters,
            reader => {
                context.FirstName = reader["FirstName"].ToString();
                context.LastName = reader["LastName"].ToString();
                context.Email = reader["Email"].ToString();
            });
    }
    catch(Exception ex)
    {
        throw new ArgumentException(ex.Message);
    }
    return context;
}

简要说明:

您会注意到代码的整体结构与您已有的非常相似。唯一的变化是:

  • GetInformationDataReader() 方法 接受 一个 Action<SqlDataReader> 委托,而不是返回 SqlDataReader .
  • GetInformationDataReader() 方法中,在正确的时间调用委托,同时连接仍处于打开状态。
  • 修改对 GetInformationDataReader() 的调用以作为委托传入代码块。

这种模式对这些情况很有用。它使代码可重用,保持代码非常干净和独立,并且不会阻止您从 using 构造中受益以避免 resource/connection 泄漏。