跳转至

检索和修改数据

一、DataReader检索数据⚓︎

01 DataReader⚓︎

可以使用 DataReader 从数据库中检索只读、只进的数据流。 查询结果在查询执行时返回,在并存储在客户端的网络缓冲区中,直到使用 DataReader 的 Read 方法对它们发出请求。 使用 DataReader 可以提高应用程序的性能,原因是它只要数据可用就立即检索数据,并且(默认情况下)一次只在内存中存储一行,减少了系统开销。

每个数据提供程序都具有一个DbDataReader 对象 1OleDbDataReaderSqlDataReaderOdbcDataReaderOracleDataReader )。

如何选择 DataSet 和 DataReader ?

使用 DataSet 可执行以下操作:
1⃣ 在应用程序中将数据缓存在本地,以便可以对数据进行处理。 如果只需要读取查询结果,则 DataReader 是更好的选择。
2⃣ 在层间或从 XML Web services 对数据进行远程处理。
3⃣ 与数据进行动态交互,例如绑定到 Windows 窗体控件或组合并关联来自多个源的数据。
4⃣ 对数据执行大量的处理,而不需要与数据源保持打开的连接,从而将该连接释放给其他客户端使用。

如果不需要 DataSet 所提供的功能,则可以通过使用 DataReader 以只进、只读方式返回数据,从而提高应用程序的性能。 虽然 DataAdapter 使用 DataReader 来填充 DataSet 的内容,但使用DataReader 可以提升性能,因为这样可以节省 DataSet 所使用的内存,并将省去创建 DataSet 并填充其内容所需的处理。

02 检索数据⚓︎

使用 DataReader 检索数据需要先创建 Command 对象的实例,然后调用 ExecuteReader() 方法创建一个 DataReader 实例,以便从数据源检索行。 DataReader 提供未缓冲的数据流,该数据流使过程逻辑可以有效地按顺序处理从数据源中返回的结果。

Tip

由于数据不在内存中缓存,所以在检索大量数据时,DataReader 是一种适合的选择。

下面的示例给出了使用 DataReader 从 SqlServer 检索数据的方法:

static void HasRows(SqlConnection connection)
{
    using (connection)
    {
        string queryString = "SELECT CategoryID, CategoryName FROM Categories;";
        SqlCommand command = new SqlCommand(queryString, connection);
        connection.Open();

        // 创建 DataReader 实例
        SqlDataReader reader = command.ExecuteReader();

        if (reader.HasRows)
        {
            while (reader.Read())
            {
                Console.WriteLine("{0}\t{1}", reader.GetInt32(0),
                    reader.GetString(1));
            }
        }
        else
        {
            Console.WriteLine("No rows found.");
        }
        reader.Close();
    }
}

每次使用完 DataReader 对象后都应调用 Close 方法关闭它。如果 Command 包含输出参数或返回值,那么在 DataReader 关闭之前,将无法访问这些值 。

Warning

当 DataReader 打开时,该 DataReader 将以独占方式使用 Connection 。 在原始 DataReader 关闭之前,将无法对 Connection 执行任何命令(包括创建另一个 DataReader) 。

03 多结果集检索⚓︎

DataReader 可以返回多个检索的结果集合,这种场景下可以调用实例的 NextResult() 方法来按顺序循环访问这些结果集。

以下示例显示 SqlDataReader 如何使用 ExecuteReader 方法处理两个 SELECT 语句的结果:

static void RetrieveMultipleResults(SqlConnection connection)
{
    using (connection)
    {
        string queryString1 = "SELECT CategoryID, CategoryName FROM dbo.Categories;";
        string queryString2 = "SELECT EmployeeID, LastName FROM dbo.Employees";
        SqlCommand command = new SqlCommand(queryString1 + queryString2, connection);
        connection.Open();
        SqlDataReader reader = command.ExecuteReader();
        while (reader.HasRows)
        {
            Console.WriteLine("\t{0}\t{1}", reader.GetName(0),
                reader.GetName(1));

            while (reader.Read())
            {
                Console.WriteLine("\t{0}\t{1}", reader.GetInt32(0),
                    reader.GetString(1));
            }
            // 访问下一个结果集
            reader.NextResult();
        }
    }
}

二、DataAdapter操纵数据⚓︎

01 DataAdapter⚓︎

DataAdapter 用于从数据源检索数据并填充 DataSet 中的表。 DataAdapter 还可将对 DataSet 所做的更改解析回数据源。 DataAdapter 使用 Connection 对象连接到数据源,并使用 Command 对象从数据源检索数据以及将更改解析回数据源。

DataAdapter 具有重要的属性:

  • SelectCommand :用于从数据源中检索数据;
  • InsertCommand :用于按照对 DataSet 中数据的插入到数据源;
  • UpdateCommand :用于按照对 DataSet 中数据的更新到数据源;
  • DeleteCommand :用于按照对 DataSet 中数据的删除数据源中的数据。

DataAdapter 具有重要的方法:

  • Fill() :用于填充数据集,添加或刷新行以与数据源中的行匹配;
  • Update() :为指定的 DataTable 中每个已插入、已更新或已删除的行调用相应的 INSERT、UPDATE 或 DELETE 语句。

每个数据提供程序都具有一个 DbDataAdapter 对象2OleDbDataAdapterSqlDataAdapterOdbcDataAdapterOracleDataAdapter)。

02 填充数据集⚓︎

DataSet 表示整个数据集,其中包含表、约束和表之间的关系。 由于 DataSet 独立于数据源,因此其可以包含应用程序本地的数据,也可以包含来自多个数据源的数据。DataAdapter 则用来控制与现有数据源的交互。

DataAdapter 使用 Fill() 方法用来填充数据集,方法将需要被填充的数据集对象(DataSet或DataTable)作为参数。

下面的示例演示了 Fill() 方法的用法:

// Assumes that connection is a valid SqlConnection object.  
string queryString = "SELECT CustomerID, CompanyName FROM dbo.Customers";  
SqlDataAdapter adapter = new SqlDataAdapter(queryString, connection);  

DataSet customers = new DataSet();
// 填充数据集
adapter.Fill(customers, "Customers");

Warning

此示例中所示的代码不显式打开和关闭 Connection 连接,Fill 方法若发现连接尚未打开,它将隐式地打开,并在完成后关闭连接。

处理单一操作时,这样可以简化代码,但如果执行多项需要打开连接的操作则需要显式地开关连接(调用 Open 和 Close 方法)以提升程序性能。

应尝试使数据源的连接打开的时间尽可能短,以便释放资;源供其他客户端应用程序使用。

03 更新数据源⚓︎

DataAdapter 的 Update() 方法可以将  DataSet 中的更改解析回数据源。该方法接受 DataSet 或 DataTable 实例为参数,这个实例包含已做更改。当调用 Update() 方法时,DataAdapter 会分析已做的更改并执行相应的命令(INSERT、UPDATE 或 DELETE)。

下列展示了更新数据源的示例:

private static void AdapterUpdate(string connectionString)
{
    using (SqlConnection connection =
               new SqlConnection(connectionString))
    {
        SqlDataAdapter dataAdapter = new SqlDataAdapter(
          "SELECT CategoryID, CategoryName FROM Categories",
          connection);

        dataAdapter.UpdateCommand = new SqlCommand(
           "UPDATE Categories SET CategoryName = @CategoryName " +
           "WHERE CategoryID = @CategoryID", connection);

        dataAdapter.UpdateCommand.Parameters.Add(
           "@CategoryName", SqlDbType.NVarChar, 15, "CategoryName");

        SqlParameter parameter = dataAdapter.UpdateCommand.Parameters.Add(
          "@CategoryID", SqlDbType.Int);
        parameter.SourceColumn = "CategoryID";
        parameter.SourceVersion = DataRowVersion.Original;

        DataTable categoryTable = new DataTable();
        dataAdapter.Fill(categoryTable);

        DataRow categoryRow = categoryTable.Rows[0];
        categoryRow["CategoryName"] = "New Beverages";

        dataAdapter.Update(categoryTable);

        Console.WriteLine("Rows after update.");
        foreach (DataRow row in categoryTable.Rows)
        {
            {
                Console.WriteLine("{0}: {1}", row[0], row[1]);
            }
        }
    }
}

04 查询分页⚓︎

查询结果分页是以较小数据子集(即页)的形式返回查询结果的过程。 它通常用于以易于管理的小块形式向用户显示结果。

DataAdapter 提供通过重载 Fill() 方法仅返回一页数据的功能。 但是,对于大量的查询结果,它可能并不是首选的分页方法,因为 DataAdapter 虽然仅使用所请求的记录来填充 DataTable 或 DataSet,但仍会使用返回整个查询的资源。 若要在从数据源中返回一页数据时不使用返回整个查询的资源,请为查询指定附加条件,使返回的行数减少到只返回所需的行。

若要使用 Fill() 方法返回一页数据,请指定 startRecord 参数(代表该数据页中的第一个记录),并指定 maxRecords 参数(代表该数据页中的记录数)。

int currentIndex = 0;  
int pageSize = 5;  

string orderSQL = "SELECT * FROM Orders ORDER BY OrderID";  
// Assumes that connection is a valid SqlConnection object.  
SqlDataAdapter adapter = new SqlDataAdapter(orderSQL, connection);  

DataSet dataSet = new DataSet();  
adapter.Fill(dataSet, currentIndex, pageSize, "Orders");

在上例中,DataSet 只填充了 5 个记录,但却返回了整个 Orders 表 。 若要用相同的 5 个记录填充 DataSet 但仅返回这 5 个记录,请在 SQL 语句中使用 TOP 和 WHERE 子句,如以下代码示例所示。

int pageSize = 5;  

string orderSQL = "SELECT TOP " + pageSize +
  " * FROM Orders ORDER BY OrderID";  
SqlDataAdapter adapter = new SqlDataAdapter(orderSQL, connection);  

DataSet dataSet = new DataSet();  
adapter.Fill(dataSet, "Orders");

请注意,当以这种方式进行查询结果分页时,必须保留用于对行进行排序的唯一标识符,以便将唯一 ID 传递给用于返回下一页记录的命令,如以下代码示例所示。

string lastRecord = dataSet
    .Tables["Orders"]
    .Rows[pageSize - 1]["OrderID"]
    .ToString();  

05 数据更改事件⚓︎

ADO.NET  DataAdapter 公开三个可用于响应数据源中数据更改的事件。

事件 描述
RowUpdating 将要开始对某行执行 UPDATE、INSERT 或 DELETE 操作(通过调用 Update 方法之一)
RowUpdated 对某行的 UPDATE、INSERT 或 DELETE 操作(通过调用 Update 方法之一)已完成。
FillError 执行 Fill 操作期间出错。

  1. 用于 OLE DB 的  OleDbDataReader 对象;用于 SQL Server 的 SqlDataReader 对象;用于 ODBC 的  OdbcDataReader 对象;用于 Oracle 的 OracleDataReader 对象。 

  2. 用于 OLE DB 的 OleDbDataAdapter 对象;用于 SQL Server 的 SqlDataAdapter 对象;用于 ODBC 的 OdbcDataAdapter 对象;用于 Oracle 的 OracleDataAdapter 对象。