65.9K
CodeProject 正在变化。 阅读更多。
Home

DataSet 入门和 XML 文件操作

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (16投票s)

2002年7月18日

8分钟阅读

viewsIcon

359333

downloadIcon

6650

本文将为您介绍 .NET 的 DataSet 对象以及如何与 XML 文件配合使用它们。

引言

本文将为您介绍如何使用 DataSet 以及如何将其与 XML 文件结合使用。当您需要共享数据源中的数据并且考虑使用 XML 时,与它们配合使用会使您的工作变得更加轻松。

系统要求

要编译该解决方案,您需要安装 Microsoft Visual Studio .NET。要运行任何客户端可执行文件,您需要安装 .NET Framework。

提供的示例是一个用 C# 编写的简单应用程序。它显示了一个带有 DataGrid 的窗体。默认情况下,该应用程序连接到 SQL Server,绑定 Northwind 数据库,并从 Customers 表中检索一些父记录,从 Orders 表中检索一些子记录。默认情况下,该应用程序假定您的计算机上安装了 SQL Server 实例。如果不是这种情况,您必须手动修改连接字符串,然后重新生成示例。

Screenshot 1 Scrennshot 2

然后,您可以将数据集保存到 XML 文件。架构信息也会被保存。

什么是 DataSet?

DataSet 对象是一个非常通用的对象,可以非常高效地存储数据库中的缓存数据。它是 System::Data 命名空间的一个成员。

一个显而易见的问题是:何时使用 DataSet?答案是:取决于情况。您应该考虑 DataSet 是内存中缓存数据的集合。所以,当您在以下情况下时,使用 DataSet 会比较好:

  • 您正在处理多个独立表或来自不同数据源的表。
  • 您正在与其他应用程序(如 Web 服务)交换数据。
  • 您对数据库中的记录执行大量处理。如果您每次需要更改某个内容时都使用 SQL 查询,则处理每条记录可能会导致连接保持打开状态,这可能会影响性能。
  • 您想对数据执行 XML/XSLT 操作。

如果您在以下情况下,不应该使用 DataSet:

  • 您的应用程序中使用 Web Forms,因为 Web Forms 及其控件在每次客户端请求页面时都会重新创建。因此,除非您计划在往返之间缓存 DataSet,否则每次创建、填充和销毁 DataSet 都会效率低下。

DataSet 对象有一个 DataTableCollection 对象作为成员,它只不过是 DataTable 对象的集合。您这样声明一个 DataSet 对象并向其中添加表(在 Managed C++ 中)

  // Declare the DataSet object

  DataSet* MyDataSet = new DataSet ("MyDataSet"); // give it a name here


  // Add two tables to it

  // - add a Table named Table1

  DataTable* Table1 = MyDataSet->Tables->Add ("Table1"); 
  
  // - add a Table named Table2

  DataTable* Table2 = MyDataSet->Tables->Add ("Table2"); 

稍后,您可以通过 Add 方法返回的指针来引用这些表,或者像这样:

  DataTable* table = MyDataSet->Tables->Item[0]; // or 

  DataTable* table = MyDataSet->Tables->Item["Table1"]; 
                                                  // isn't this indexation cool?

DataTable 对象有两个重要成员:RowsColumnsRows 是一个 DataRowCollection 对象,Columns 是一个 DataColumnCollection 对象。DataRowCollectionDataRow 对象的集合,DataColumnCollectionDataColumn 对象的集合。我相信您可以轻松弄清楚这些对象代表什么。:)

向数据集中添加数据非常简单。

  // adding data to the first table in the DataSet

  DataTable* Table1 = MyDataSet->Tables->Item[0];

  // add two columns to the table

  Table1->Columns->Add ("Column1");
  Table2->Columns->Add ("Column2");

  // get the collection of rows

  DataRowCollection* drc = Table1->Rows;

  // create a vector of Objects that we will insert in current row

  Object* obj[] = new Object* [2];
  
  obj[0] = new String ("Item 1");
  obj[1] = new String ("Item 2");

  // add them to the dataset

  drc->Add (obj);

如果您想指定某个列的数据类型,应该使用 DataColumn::DataType 属性。您可以设置以下任何数据类型:BooleanByteCharDateTimeDecimalDoubleInt16Int32Int64SByteSingleStringTimeSpanUInt16UInt32UInt64

就是这样!嗯,如果您想手动构建数据集,这就是您需要做的全部。如果您想连接到真实的数据源,那么做法就不是这样了。

绑定数据库

要连接到数据库服务器(如 SQL Server)并从中填充数据集,您需要三个额外的对象:ConnectionDataCommandDataAdapter 对象。

Connection 对象用于连接数据库对象。您必须为其提供一个连接字符串。此外,如果您的应用程序正在使用事务,则必须将事务对象附加到连接。

DataCommand 对象用于将命令发送到数据库服务器。它包含四个 System::String 对象,分别名为 SelectCommandInsertCommandUpdateCommandDeleteCommand。我相信这些字符串对象代表什么非常明显,无非是四种基本 SQL 操作。

DataAdapter 对象是执行所有魔幻工作的对象。它将数据从数据库填充到 DataSet,并将 DataSet 中的数据更新到数据库。

您现在可能想知道与上述对象对应的类是什么。嗯,Microsoft 准备了两组类 - 您不能说 .NET 让生活变得更轻松,对吧?:)。第一组基于 OLE DB,是 System::Data::OleDb 命名空间的一部分。它包含以下类:OleDbConnectionOleDbCommandOleDbDataAdapter。另一组类针对与 Microsoft SQL Server 的工作进行了优化。它是 System::Data::SqlClient 命名空间的一部分,其类包括:SqlConnectionSqlCommandSqlDataAdapter

这里有一个连接数据库并填充数据集的完整示例。我使用了为 SQL Server 优化的类。如果您需要使用 OLE DB,只需将“Sql”替换为“OleDb”即可。我们尝试获取两个表 Table1Table2,并在它们之间设置父子关系。

  // Create a database connection string

  String* str = new String ("user id=sa;password=;initial catalog=MyDB;"
                            "data source=(local)");
  
  // Create the database connection object

  SqlConnection* sqlcon = new SqlConnection (str);

  sqlcon->Open (); // open it


  // create the first SQL query

  String* strTable1 = String::Format ("SELECT * FROM Table1 " 
    "WHERE Field1 = {0}", FieldID.ToString ());    // FieldID is 

                   // an interger used to filter our query.


  // create the second SQL query. It joins the first table to select only those

  // fields in relation

  String* strTable2 = String::Format ("SELECT T2.* FROM Table2 T2 "
                   "INNER JOIN Table1 T1 ON T2.ParendID = T1.ID "
                   "WHERE T1.Field1 = {0}", Field1.ToString ());    // FieldID is 

                   // an interger used to filter our query.


  // create the SQL command objects. We pass in the contructor the 

  // SqlConnection object and the query string

  SqlCommand* sqlTable1 = new SqlCommand (strTable1, sqlcon);
  SqlCommand* sqlTable2 = new SqlCommand (strTable2, sqlcon);

  // Create a data adapter for every table. We pass the SqlComand objects as parameters

  SqlDataAdapter* Table1Adapter = new SqlDataAdapter (sqlTable1);
  SqlDataAdapter* Table2Adapter = new SqlDataAdapter (sqlTable2);
  

  // now we create the dataset object and we give it a name

  DataSet* MyDataSet = new DataSet ("MyDataSet");

  // we inform the dataset object about the tables it is going to contain

  // by adding those tables to the dataset. 


  DataTable* Table1 = BackupDataSet->Tables->Add ("Table1");
  DataTable* Table2 = BackupDataSet->Tables->Add ("Table2");

  // now we are filling the Dataset using the Dataadapter objects

  // We need not say anything to the DataSet object about the 

  // columns of the table and their data type. The DataAdapter objects

  // takes care of everything.


  Table1Adapter->Fill (Table1);
  Table2Adapter->Fill (Table2);

  // To ensure relationships between Tables we must add a DataRelation object

  // We assume that between column 0 in Table1 and column 1 in Table2 there is 

  // a one-to-many relationship


  MyDataSet->Relations->Add (Table1->Columns->Item[0],
                             Table2->Columns->Item[1]);

有关关系和约束的详细信息,您应该阅读有关 Constraint 类及其派生的两个类 ForeignKeyConstraintUniqueConstraint

处理 XML 文件

DataSet 对象可以非常轻松地与 XML 文件配合使用。有两种方法可以序列化 DataSet 对象。它们是 DataSet::WriteXmlDataSet::WriteXmlSchema。第一个方法将数据写入 XML 文件,并且可能包含架构信息。当您想写入包含嵌入式架构信息的 XML 文件时,它很有用。但是,如果您更喜欢将架构信息放在单独的 (.xsd) 文件中,则应使用 DataSet::WriteXmlSchema 方法。

System::Xml 命名空间中也有许多类:XmlWriterXmlReaderXmlTextWriterXmlDataDocument 等等。您可以将它们与数据集一起使用以执行一些高级 XML 操作。例如,如果您想将数据集写入 XML 文件,您可以使用

  // recevies a DataSet in constructor

  XmlDataDocument* xmlDoc = new XmlDataDocument(MyDataSet);
  XmlTextWriter* Output = new XmlTextWriter ("C:\\Myfile.xml", NULL);

  // perform some formatting

  Output->Formatting = Formatting::Indented;
  Output->Indentation = 2;

  // and write it

  xmlDoc->WriteTo (Output);
  Output->Close ();

或者直接使用 DataSet::WriteXml 方法

MyDataSet->WriteXml ("C:\\MyFile.xml", XmlWriteMode::WriteSchema);

在后一种情况下,我选择通过使用 XmlWriteMode 枚举的一个成员来嵌入架构信息。该枚举的其他字段是 XmlWriteMode::IgnoreSchema(如果您不想包含架构信息)、XmlWriteMode::DiffGram(如果您想包含数据集的原始值和任何更改)。要读取 XML 文件,我们使用另一个枚举:XmlReadMode

DataRelation 有一个名为 Nested 的属性。当此属性设置为 true 时,每次 DataSet 写入父表的记录时,它还会嵌套所有子表中对应的所有记录。

格式化 XML 文件中的数据非常灵活。默认情况下,为每个表中的每个列创建一个新元素。假设您有一个名为 Table1 的表,其中有两个名为 IDName 的列,XML 文件的默认输出将是:

  <MyDataSet>
    <Table1>
      <ID>7</ID>
      <Name>name 1</Name>
    </Table1>
    <Table1>
      <ID>8</ID>
      <Name>name 2</Name>
    </Table1>
  </MyDataSet>

如果一个列要成为节点的属性,则需要设置 DataColumn 类的 ColumnMapping 属性。为此,您必须查看 MappingType 枚举。其字段为:AttributeElementHiddenSimpleContent。选择 Attribute 会将相应的列作为父节点的属性写入。输出将如下所示:

  <MyDataSet>
    <Table1 ID="7">
      <Name>name 1</Name>
    </Table1>
    <Table1 ID="8">
      <Name>name 2</Name>
    </Table1>
  </MyDataSet>

SimpleContent 意味着不会写入单个列的标签。如果您选择 Hidden,则该列根本不会被写入。

当然,您可以非常轻松地将它们组合起来。这样做:

  Table1->Columns->Item[0]->ColumnMapping = MappingType::Attribute;
  Table2->Columns->Item[1]->ColumnMapping = MappingType::SimpleContent;

将产生以下结果:

  <MyDataSet>
    <Table1 ID="7">name1</Table1>
    <Table1 ID="8">name2</Table1>
  </MyDataSet>

从 XML 文件读取数据与写入数据一样简单。最简单的方法是使用 ReadXml 方法,如下所示:

  MyDataSet->ReadXml ("C:\\MyFile.xml", XmlReadMode::ReadSchema);

我还从文件中读取了架构信息。这意味着 DataSet 将自动检测数据集中所有列的数据类型,以及表之间的任何约束或关系。如果您想更新数据集,这确实很棒。您更改父表中的一个值,所有子表都会更新父值。此外,尝试更改子表中没有父值的值将引发异常。

更新数据库

将数据读入数据集然后更新数据库,与从数据源读取数据并填充数据集一样简单。假设您正在从 XML 文件读取数据并更新 SQL Server,您必须执行以下操作:

  • 创建 DataSet 对象并读取 XML 文件。如果您的 XML 文件包含任何架构信息,则 DataSet 将自动检测并创建相应的表并启用任何约束。
  • 为要更新的每个表创建一个 DataAdapter 对象。
  • 为每个 DataAdapter 对象调用 Update 方法。

请记住,我提到了 SqlDataAdapter 类中的四个 String 成员?它们是:SelectCommandInsertCommandUpdateCommandDeleteCommandSelectCommand 是用于从数据库中获取数据的查询。如果您想执行自定义更新/插入/删除操作,可以定义其他三个对象。如果您不想这样做,可以使用 SqlCommandBuilderOleDbCommandBuilder 类。该类将自动构建这些字符串。

每次向数据库写入数据时,使用事务以防止并发写入数据库是一个好习惯。为此,.NET Framework 提供了两个类:OleDbTransactionSqlTransaction

这是一个从 XML 文件读取数据并将其写入 SQL Server 的示例。

  SqlTransaction* SqlTrans; // declare a transaction object

  try
  {
    String* str = new String ("user id=sa;password=;initial catalog=MyDB;"
                              "data source=(local)");
    
    // Create the database connection object

    SqlConnection* sqlcon = new SqlConnection (str);

    sqlcon->Open (); // open it


    // create the data set object and give it a name

    DataSet* MyDataSet = new DataSet ("MyDataSet");

    // read the XML file

    // I have also read the schema information file

    MyDataSet->ReadXml ("C:\\MyXmlFile.xml", XmlReadMode::ReadSchema);

    // Begin the transaction

    SqlTransaction = sqlcon->BeginTransaction ();

    // create the data adapters

    SqlDataAdapter* Table1Adapter = new SqlDataAdapter("SELECT * FROM Table1", sqlcon);
    SqlDataAdapter* Table2Adapter = new SqlDataAdapter("SELECT * FROM Table2", sqlcon);

    // we have provided only the SelectCommand strings. To update

    // the database we must provide the DeleteCommand, InsertCommand and

    // UpdateCommand also.

    // This can be done automatically with the command builder


    // create the command builders for each data adapter

    SqlCommandBuilder* Table1Command = new SqlCommandBuilder (Table1Adapter);
    SqlCommandBuilder* Table2Command = new SqlCommandBuilder (Table2Adapter);

    // we must specify the transaction used by these adapter.

    Table1Adapter->SelectCommand->Transaction = SqlTrans;
    Table2Adapter->SelectCommand->Transaction = SqlTrans;

    // update the database

    Table1Adapter->Update (MyDataSet, "Table1");
    Table2Adapter->Update (MyDataSet, "Table2");

    // don't forget to commit


    SqlTrans->Commit ();

  }
  catch (Exception* e)
  {
    // if we have started the transaction then rollback it

    if (SqlTrans != NULL)
      SqlTrans->Rollback();
  }

结论

DataSet 提供了一种非常易于使用且功能强大的方法来处理来自不同表甚至数据源的大量数据。数据集将缓存所有数据,使其在您需要读取数据、对其执行大量操作然后更新它时非常有用。它还提供完整的 XML 支持,使您更容易在 Web 服务等应用程序之间共享数据。

© . All rights reserved.