DataSet 入门和 XML 文件操作






4.50/5 (16投票s)
2002年7月18日
8分钟阅读

359333

6650
本文将为您介绍 .NET 的 DataSet 对象以及如何与 XML 文件配合使用它们。
引言
本文将为您介绍如何使用 DataSet 以及如何将其与 XML 文件结合使用。当您需要共享数据源中的数据并且考虑使用 XML 时,与它们配合使用会使您的工作变得更加轻松。
系统要求
要编译该解决方案,您需要安装 Microsoft Visual Studio .NET。要运行任何客户端可执行文件,您需要安装 .NET Framework。
提供的示例是一个用 C# 编写的简单应用程序。它显示了一个带有 DataGrid
的窗体。默认情况下,该应用程序连接到 SQL Server,绑定 Northwind 数据库,并从 Customers 表中检索一些父记录,从 Orders 表中检索一些子记录。默认情况下,该应用程序假定您的计算机上安装了 SQL Server 实例。如果不是这种情况,您必须手动修改连接字符串,然后重新生成示例。
然后,您可以将数据集保存到 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
对象有两个重要成员:Rows
和 Columns
。Rows
是一个 DataRowCollection
对象,Columns
是一个 DataColumnCollection
对象。DataRowCollection
是 DataRow
对象的集合,DataColumnCollection
是 DataColumn
对象的集合。我相信您可以轻松弄清楚这些对象代表什么。:)
向数据集中添加数据非常简单。
// 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
属性。您可以设置以下任何数据类型:Boolean
、Byte
、Char
、DateTime
、Decimal
、Double
、Int16
、Int32
、Int64
、SByte
、Single
、String
、TimeSpan
、UInt16
、UInt32
、UInt64
。
就是这样!嗯,如果您想手动构建数据集,这就是您需要做的全部。如果您想连接到真实的数据源,那么做法就不是这样了。
绑定数据库
要连接到数据库服务器(如 SQL Server)并从中填充数据集,您需要三个额外的对象:Connection
、DataCommand
和 DataAdapter
对象。
Connection
对象用于连接数据库对象。您必须为其提供一个连接字符串。此外,如果您的应用程序正在使用事务,则必须将事务对象附加到连接。
DataCommand
对象用于将命令发送到数据库服务器。它包含四个 System::String
对象,分别名为 SelectCommand
、InsertCommand
、UpdateCommand
和 DeleteCommand
。我相信这些字符串对象代表什么非常明显,无非是四种基本 SQL 操作。
DataAdapter
对象是执行所有魔幻工作的对象。它将数据从数据库填充到 DataSet
,并将 DataSet
中的数据更新到数据库。
您现在可能想知道与上述对象对应的类是什么。嗯,Microsoft 准备了两组类 - 您不能说 .NET 让生活变得更轻松,对吧?:)。第一组基于 OLE DB,是 System::Data::OleDb
命名空间的一部分。它包含以下类:OleDbConnection
、OleDbCommand
和 OleDbDataAdapter
。另一组类针对与 Microsoft SQL Server 的工作进行了优化。它是 System::Data::SqlClient
命名空间的一部分,其类包括:SqlConnection
、SqlCommand
和 SqlDataAdapter
。
这里有一个连接数据库并填充数据集的完整示例。我使用了为 SQL Server 优化的类。如果您需要使用 OLE DB,只需将“Sql”替换为“OleDb”即可。我们尝试获取两个表 Table1 和 Table2,并在它们之间设置父子关系。
// 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
类及其派生的两个类 ForeignKeyConstraint
和 UniqueConstraint
。
处理 XML 文件
DataSet
对象可以非常轻松地与 XML 文件配合使用。有两种方法可以序列化 DataSet
对象。它们是 DataSet::WriteXml
和 DataSet::WriteXmlSchema
。第一个方法将数据写入 XML 文件,并且可能包含架构信息。当您想写入包含嵌入式架构信息的 XML 文件时,它很有用。但是,如果您更喜欢将架构信息放在单独的 (.xsd) 文件中,则应使用 DataSet::WriteXmlSchema
方法。
System::Xml
命名空间中也有许多类:XmlWriter
、XmlReader
、XmlTextWriter
、XmlDataDocument
等等。您可以将它们与数据集一起使用以执行一些高级 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 的表,其中有两个名为 ID 和 Name 的列,XML 文件的默认输出将是:
<MyDataSet>
<Table1>
<ID>7</ID>
<Name>name 1</Name>
</Table1>
<Table1>
<ID>8</ID>
<Name>name 2</Name>
</Table1>
</MyDataSet>
如果一个列要成为节点的属性,则需要设置 DataColumn
类的 ColumnMapping
属性。为此,您必须查看 MappingType
枚举。其字段为:Attribute
、Element
、Hidden
和 SimpleContent
。选择 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
成员?它们是:SelectCommand
、InsertCommand
、UpdateCommand
和 DeleteCommand
。SelectCommand
是用于从数据库中获取数据的查询。如果您想执行自定义更新/插入/删除操作,可以定义其他三个对象。如果您不想这样做,可以使用 SqlCommandBuilder
或 OleDbCommandBuilder
类。该类将自动构建这些字符串。
每次向数据库写入数据时,使用事务以防止并发写入数据库是一个好习惯。为此,.NET Framework 提供了两个类:OleDbTransaction
和 SqlTransaction
。
这是一个从 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 服务等应用程序之间共享数据。