DekGenius.com
[ Team LiB ] Previous Section Next Section

11.2 Manipulating Data Offline

Despite all these classes, there are times when you won't want to write SQL for every database operation, and you'll want to be able to manipulate entire sets of data as a whole without maintaining an open database connection. The mechanism for such operations is the DataSet.

A DataSet is an in-memory representation of a database. Just as a database contains tables, a DataSet contains a DataTableCollection (a collection of DataTable objects). Just as tables are related to one another, a DataSet contains a DataRelationCollection (a collection of DataRelation objects). Each DataTable contains a DataColumnCollection, which represents the table's columns, and a DataRowCollection, which represents the table's rows. The DataTable also contains references to various DataRelation and Constraint objects, which reflect the underlying table's relations and constraints. You can create a DataSet from scratch and fill it with data, or you can use an IDbDataAdapter to map a DataSet to a database.

Figure 11-2 shows the structure of the DataSet class and its related classes.

Figure 11-2. The DataSet object model
figs/dnxm_1102.gif

Here's where the talk about the databases meets the subject of this book, XML. You can serialize the data and structure of a DataSet to XML. You can generate a DataSet subclass from an XML Schema. You can read data from a DataSet as if it were an XML document. And finally, you can use the DataSet to track changes to a database using the DiffGram, which is, you guessed it, an XML document.

11.2.1 Creating a DataSet

The most obvious way to create a DataSet is to construct each of its objects and add them to the appropriate collections. First, create a new instance of DataSet named "AngusHardware." The DataSet represents the entire database schema:

DataSet dataSet = new DataSet("AngusHardware");

Next, add a table named "customers" to the DataSet. The DataTableCollection.Add( ) method has several overloads; by passing a string parameter, you're creating a new DataTable with the given name, and adding it to the DataSet's Tables property. Add( ) returns the newly created DataTable, which you'll use to create columns:

DataTable customers = dataSet.Tables.Add("customers");

Next, add a column to the "customers" table. The DataColumnCollection.Add( ) method returns the newly created DataColumn, which you'll use in a minute to assign the primary key. This Add( ) method, like the one on DataTableCollection, has several overloads. The one used here simply takes the name of the database column and the Type of the data it contains:

DataColumn customersCustomerId = customers.Columns.Add("customer_id",
   typeof(long));

The process is similar for each column. Note that some columns are nullable in the database and others are not; the AllowDBNull property indicates whether the column is nullable:

customers.Columns.Add("name",typeof(string)).AllowDBNull = false;
customers.Columns.Add("address1",typeof(string)).AllowDBNull = false;
customers.Columns.Add("address2",typeof(string));
customers.Columns.Add("address3",typeof(string));
customers.Columns.Add("city",typeof(string)).AllowDBNull = false;
customers.Columns.Add("state",typeof(string)).AllowDBNull = false;
customers.Columns.Add("zip",typeof(string)).AllowDBNull = false;

The last step for the "customers" table is to set the primary key, using the customersCustomerId DataColumn created a minute ago. Although this table has a simple, one-column primary key, the DataSet allows for concatenated primary keys via an array of DataColumn objects:

customers.PrimaryKey = new DataColumn [ ] {customersCustomerId};

A very similar process creates the "coupons" table:

DataTable coupons = dataSet.Tables.Add("coupons");
DataColumn couponCouponCode = coupons.Columns.Add("coupon_code",   
  typeof(string));
coupons.Columns.Add("discount_amount", 
  typeof(Double)).AllowDBNull = false;
coupons.Columns.Add("discount_type", typeof(int)).AllowDBNull = false;
coupons.Columns.Add("expiration_date", 
  typeof(DateTime)).AllowDBNull = false;
coupons.PrimaryKey = new DataColumn [ ] {couponCouponCode};

And again for the "coupon_redemptions" table:

DataTable couponRedemptions = 
  dataSet.Tables.Add("coupon_redemptions");
DataColumn couponRedemptionsCouponCode = 
  couponRedemptions.Columns.Add("coupon_code", typeof(string));
couponRedemptions.Columns.Add("total_discount", typeof(Double)).AllowDBNull = false;
couponRedemptions.Columns.Add("redemption_date", typeof(DateTime)).AllowDBNull = false;
DataColumn couponRedemptionsCustomerId = 
  couponRedemptions.Columns.Add("customer_id", typeof(long));

Now that all the tables are created, it's time to assign the relations between them. The DataSet has a DataRelationCollection, whose Add( ) method has several overloads. The one used here takes the parent DataColumn and the child DataColumn. There are two relations in this example; one between coupons.coupon_code and coupon_redemptions.coupon_code, and one between customers.customer_id and coupon_redemptions.customer_id:

dataSet.Relations.Add(couponCouponCode, couponRedemptionsCouponCode);
dataSet.Relations.Add(customersCustomerId, 
  couponRedemptionsCustomerId);

Finally, this line writes an XML Schema document that describes the DataSet to a file:

dataSet.WriteXmlSchema("Coupons.xsd");

The XML Schema that this program saved in Coupons.xsd is a normal XML Schema, and it can be used to recreate the DataSet in memory. The code to read in a DataSet's structure from a schema is very simple. In fact, it can be expressed succinctly in two statements:

DataSet dataSet = new DataSet( );
dataSet.ReadXmlSchema("Coupons.xsd");

Example 11-4 shows the complete program that creates the DataSet for the coupon database, and saves an XML Schema for it.

Example 11-4. Creating a DataSet for the coupon database
using System;
using System.Data;

public class CreateDataSet {
  public static void Main(string [ ] args) {

    DataSet dataSet = new DataSet("AngusHardware");

    DataTable customers = dataSet.Tables.Add("customers");
    DataColumn customersCustomerId = customers.Columns.Add("customer_id",
      typeof(long));
    customers.Columns.Add("name",typeof(string)).AllowDBNull = false;
    customers.Columns.Add("address1",typeof(string)).AllowDBNull = false;
    customers.Columns.Add("address2",typeof(string));
    customers.Columns.Add("address3",typeof(string));
    customers.Columns.Add("city",typeof(string)).AllowDBNull = false;
    customers.Columns.Add("state",typeof(string)).AllowDBNull = false;
    customers.Columns.Add("zip",typeof(string)).AllowDBNull = false;
    customers.PrimaryKey = new DataColumn [ ] {customersCustomerId};

    DataTable coupons = dataSet.Tables.Add("coupons");
    DataColumn couponCouponCode = coupons.Columns.Add("coupon_code",   
      typeof(string));
    coupons.Columns.Add("discount_amount", 
      typeof(Double)).AllowDBNull = false;
    coupons.Columns.Add("discount_type", typeof(int)).AllowDBNull = false;
    coupons.Columns.Add("expiration_date", 
      typeof(DateTime)).AllowDBNull = false;
    coupons.PrimaryKey = new DataColumn [ ] {couponCouponCode};

    DataTable couponRedemptions = 
      dataSet.Tables.Add("coupon_redemptions");
    DataColumn couponRedemptionsCouponCode = 
      couponRedemptions.Columns.Add("coupon_code", typeof(string));
    couponRedemptions.Columns.Add("total_discount",
      typeof(Double)).AllowDBNull = false;
    couponRedemptions.Columns.Add("redemption_date", 
      typeof(DateTime)).AllowDBNull = false;
    DataColumn couponRedemptionsCustomerId = 
      couponRedemptions.Columns.Add("customer_id", typeof(long));

    dataSet.Relations.Add(couponCouponCode, couponRedemptionsCouponCode);
    dataSet.Relations.Add(customersCustomerId, 
      couponRedemptionsCustomerId);

    dataSet.WriteXmlSchema("Coupons.xsd");
  }
}

11.2.2 Populating a DataSet

The DataSet is now ready to use just as if you had created it procedurally. You can create new rows in each of its tables, using the DataTable.NewRow( ) and DataTable.Rows.Add( ) methods, as shown in Example 11-5.

Example 11-5. Populating the DataSet
using System;
using System.Data;

public class CreateData {
  public static void Main(string [ ] args) {

    DataSet dataSet = new DataSet( );
    dataSet.ReadXmlSchema("Coupons.xsd");
    
    DataTable couponsTable = dataSet.Tables["coupons"];

    DataRow couponRow = couponsTable.NewRow( );
    couponRow["coupon_code"] = "763FF";
    couponRow["discount_amount"] = 0.5;
    couponRow["discount_type"] = DiscountType.Fixed;
    couponRow["expiration_date"] = new DateTime(2002,12,31);
    couponsTable.Rows.Add(couponRow);

    dataSet.WriteXml("Coupons.xml");
  }
}

Some important highlights of this program are listed below. First, a new DataSet instance is created, and its structure is populated with the saved Coupons.xsd schema:

DataSet dataSet = new DataSet( );
dataSet.ReadXmlSchema("Coupons.xsd");

Next, the "coupons" table is retrieved using the DataTableCollection's string indexer:

DataTable couponsTable = dataSet.Tables["coupons"];

You can only create a new row using the DataTable's NewRow( ) factory method. This is because the columns must be populated according to the database schema stored in the DataTable. Note that the NewRow( ) method does not actually add the new DataRow to the DataTable; that happens later:

DataRow couponRow = couponsTable.NewRow( );

Now you can access each column from the new DataRow and set its value:

couponRow["coupon_code"] = "763FF";
couponRow["discount_amount"] = 0.5;
couponRow["discount_type"] = DiscountType.Fixed;
couponRow["expiration_date"] = new DateTime(2002,12,31);

Now that the DataRow is fully populated with data, it's time to add it to the DataTable's DataRowCollection. If some constraint or relation was not satisfied at this point, a specific DataException is thrown, giving details as to what constraint or relation was violated:

couponsTable.Rows.Add(couponRow);

Finally, the last line writes the entire DataSet to an XML file:

dataSet.WriteXml("Coupons.xml");

The Coupons.xml file generated by the last line is shown in Example 11-6. You can see that it's a normal XML file, and it is valid according to the schema in Coupons.xsd.

Example 11-6. Coupons.xml file
<?xml version="1.0" standalone="yes"?>
<AngusHardware>
  <coupons>
    <coupon_code>763FF</coupon_code>
    <discount_amount>0.5</discount_amount>
    <discount_type>1</discount_type>
    <expiration_date>2002-12-31T00:00:00.0000000-05:00</expiration_date>
  </coupons>
</AngusHardware>

Remember, you can always verify that any XML file is valid according to a DTD or XML Schema with the XmlValidatingReader:

XmlSchema schema  = XmlSchema.Read(
  new FileStream("Coupons.xsd", FileMode.Open), null);

XmlValidatingReader reader = new XmlValidatingReader(
  new XmlTextReader("Coupons.xml"));
reader.Schemas.Add(schema);
reader.ValidationType = ValidationType.Schema;

while (reader.Read( )) {
  // this will throw an exception if invalid
}

You can also create an XML file that contains both the schema to define the DataSet structure and the data to populate it. The DataSet.WriteXml( ) method takes an additional optional parameter, an XmlWriteMode enumeration instance. The following list shows its values and what effect they have:


DiffGram

The output file contains a DiffGram, which is an XML format that specifies the differences between a DataSet in memory and the underlying database. I'll talk more about the DiffGram later:

<?xml version="1.0" standalone="yes"?>
<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
  <AngusHardware>
    <coupons diffgr:id="coupons1" msdata:rowOrder="0" diffgr:hasChanges="inserted">
      ...
    </coupons>
  </AngusHardware>
</diffgr:diffgram>

IgnoreSchema

Only the data are written to the output file. This is the default:

<?xml version="1.0" standalone="yes"?>
<AngusHardware>
  <coupons>
    ...
  </coupons>
</AngusHardware>

WriteSchema

The data and the schema are both written to the file:

<?xml version="1.0" standalone="yes"?>
<AngusHardware>
  <xs:schema id="AngusHardware" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="AngusHardware" msdata:IsDataSet="true">
      ...
    </xs:element>
  </xs:schema>
  <coupons>
    ...
  </coupons>
</AngusHardware>

Reading a DataSet's structure and contents is done in a similar fashion. The DataSet.ReadXml( ) method takes an optional XmlReadMode enumeration parameter. The following lists its possible values and their effects:


Auto

If the data is a DiffGram, this is equivalent to XmlReadMode.DiffGram. If the DataSet already has a schema, or if the data has an inline schema (that is, it was written with XmlWriteMode.WriteSchema), this is equivalent to XmlReadMode.ReadSchema. Otherwise, it is equivalent to XmlReadMode.InferSchema.


DiffGram

The DiffGram is read and the changes are made to the DataSet in memory.


Fragment

The data is assumed to have come directly from a SQL Server FOR XML query.


IgnoreSchema

Any inline schema in the XML file is ignored, and the data are read into the DataSet's existing schema. Any data that do not fit the schema are discarded.


InferSchema

Any inline schema in the XML file is ignored. If the DataSet in memory already has a schema, the data are loaded and any necessary tables and columns are added to the schema. In case of a namespace clash between the DataSet's schema and the inferred schema, an exception is thrown.


ReadSchema

The inline schema in the XML file is read. If the DataSet in memory already has a schema, and new tables from the XML file are added, but an exception is thrown if any tables in the inline schema already exist in the DataSet.

11.2.3 Generating a DataSet

I said the generated schema is a normal XML Schema document, and it is. It does, however, contain a few extra attributes with the msdata prefix. These attributes help the XSD tool to generate a subclass of DataSet with convenience methods to access tables and columns in a more type-safe manner. After running the CreateDataSet program, execute this command:

xsd /dataset Coupons.xsd

This is the same XSD tool that I introduced in Chapter 8.


The resulting file, Coupons.cs, contains the class AngusHardware, which extends DataSet, as well as a number of support classes. It's a much more complex structure than the one we generated in Chapter 8 and Chapter 9, and with good reason; it is used to create a DataSet, not just to load XML data with XmlSerializer.

You can see the benefit of the generated DataSet if you compare the code in Example 11-5 with that in Example 11-7.

Example 11-7. Populating a DataSet generated by xsd
using System;

public class CreateData {
  public static void Main(string [ ] args) {

    AngusHardware dataSet = new AngusHardware( );

    dataSet.coupons.AddcouponsRow(
      "763FF", 0.5, (int)DiscountType.Fixed, new DateTime(2002,12,31));

    dataSet.WriteXml("Coupons.xml");
  }
}

The generated DataSet class contains members named after the tables and columns in the schema. To start with, the name of the main class, AngusHardware, reflects the name of the DataSet. Each DataTable of the DataSet is represented by a generated private class within the DataSet; the "coupons" table, for example, is represented by the generated class AngusHardware.couponsDataTable. The AngusHardware.coupons property provides direct access to the "coupons" DataTable instance. The AngusHardware.couponsDataTable class has a method called AddcouponsRow( ), whose parameters match the columns of the table in the order in which they were added.

The object model for the generated AngusHardware Dataset is represented by Figure 11-3.

Figure 11-3. Generated DataSet object model
figs/dnxm_1103.gif

11.2.4 Connecting a DataSet to the Database

I haven't yet shown you how to actually connect the DataSet to an actual database. This is achieved using the IDataAdapter interface, which serves as an intermediate layer between the database table and the DataSet. You specify the SQL commands to select, insert, update, and delete from each table, and then use the Fill( ) method to fill the DataSet with data from the database, or the Update( ) method to update the database with data from the DataSet.

The first step is create a database connection, a SqlDataAdapter, and an AngusHardware DataSet:

SqlConnection connection = new SqlConnection(
  "Initial Catalog=AngusHardware; User ID=sa");
SqlDataAdapter adapter = new SqlDataAdapter( );
AngusHardware dataSet = new AngusHardware( );

After that, you can create the select command for the SqlDataAdapter. This is the SqlCommand that will be used to populate the DataSet with data from the database:

adapter.SelectCommand = new SqlCommand("SELECT coupon_code, " +
  "discount_amount, discount_type, expiration_date FROM coupons", 
  connection);

Because you'll be updating some of the data in this example and you would like those changes to be reflected in the database, the next step is to set the SqlDataAdapter's UpdateCommand property. Again, this is a normal SqlCommand, but unlike the SelectCommand it is necessary to add SqlParameters so that any updates get mapped to the correct columns:

adapter.UpdateCommand = new SqlCommand(
  "UPDATE coupons SET coupon_code = @couponCode, discount_amount = " +
  "@discountAmount, discount_type = @discountType, expiration_date = " +
  "@expirationDate WHERE coupon_code = @couponCode", connection);
adapter.UpdateCommand.Parameters.Add("@couponCode",
  SqlDbType.Char,10,"coupon_code");
adapter.UpdateCommand.Parameters.Add("@discountAmount",
  SqlDbType.Float,8,"discount_amount");
adapter.UpdateCommand.Parameters.Add("@discountType",
  SqlDbType.TinyInt,1,"discount_type");
adapter.UpdateCommand.Parameters.Add("@expirationDate",
  SqlDbType.DateTime,8,"expiration_date");

It's also possible to set the InsertCommand and DeleteCommand properties, but since you're only selecting and updating rows in this example, it's not necessary.


With the SqlDataAdapter all set up, the Fill( ) method is used to fill the DataSet with data from the database using the SelectCommand. The second parameter to Fill( ) tells the SqlDataAdapter the name of the DataTable to fill with data; this name can differ from the name of the database table:

adapter.Fill(dataSet, "coupons");

Updating a row of data is a simple matter of locating the row of interest and setting its properties. Here we set the expiration date to the current date and time:

dataSet.coupons[0].expiration_date = DateTime.Now;

Since some of the data were changed, the SqlDataAdapter.Update( ) method causes the database to be updated with the changes currently in the DataSet:

adapter.Update(dataSet, "coupons");

Note that although in this case the DataSet was filled, modified, and updated within the span of a single database session, the operation could just as easily have spanned a larger time. The DataSet is a disconnected view of the database, which means that a connection need not be maintained while the data are modified.

Example 11-8 shows the complete program.

Example 11-8. Creating a DataSet with IDataAdapter
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;

public class FillDataSet {
  
  public static void Main(string [ ] args) {
    
    SqlConnection connection = new SqlConnection(
      "Initial Catalog=AngusHardware; User ID=sa");
    SqlDataAdapter adapter = new SqlDataAdapter( );
    AngusHardware dataSet = new AngusHardware( );
    
    adapter.SelectCommand = new SqlCommand("SELECT coupon_code, " +
      "discount_amount, discount_type, expiration_date FROM coupons", 
      connection);

    adapter.UpdateCommand = new SqlCommand(
      "UPDATE coupons SET coupon_code = @couponCode, discount_amount = " +
      "@discountAmount, discount_type = @discountType, expiration_date = " +
      "@expirationDate WHERE coupon_code = @couponCode", connection);
    adapter.UpdateCommand.Parameters.Add("@couponCode",
      SqlDbType.Char,10,"coupon_code");
    adapter.UpdateCommand.Parameters.Add("@discountAmount",
      SqlDbType.Float,8,"discount_amount");
    adapter.UpdateCommand.Parameters.Add("@discountType",
      SqlDbType.TinyInt,1,"discount_type");
    adapter.UpdateCommand.Parameters.Add("@expirationDate",
      SqlDbType.DateTime,8,"expiration_date");

    adapter.Fill(dataSet, "coupons");

    dataSet.coupons[0].expiration_date = DateTime.Now;

    adapter.Update(dataSet, "coupons");
  }
}

11.2.5 Tracking Changes to a DataSet

When making changes to a DataSet, it is often useful to keep a record of the changes. That way you can make a set of related changes on the client machine, then transmit just the changes back to the server. This technique saves network time, because the changes are all transmitted at once, and it saves bandwidth, because only the changes are transmitted.

You could add another line of code to Example 11-8 to see that the DataSet maintains a "before" and "after" view of the data. Add this line before the Update( ) statement:

dataSet.WriteXml(Console.Out, XmlWriteMode.DiffGram);

And you'll see the following output when you run the program:

<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr=
"urn:schemas-microsoft-com:xml-diffgram-v1">
  <AngusHardware>
    <coupons diffgr:id="coupons1" msdata:rowOrder="0" diffgr:hasChanges="modified">
      <coupon_code>077GH     </coupon_code>
      <discount_amount>15</discount_amount>
      <discount_type>0</discount_type>
      <expiration_date>2002-11-09T14:17:41.6372544-05:00</expiration_date>
    </coupons>
    <coupons diffgr:id="coupons2" msdata:rowOrder="1">
      <coupon_code>665RQ     </coupon_code>
      <discount_amount>15</discount_amount>
      <discount_type>0</discount_type>
      <expiration_date>2002-11-30T00:00:00.0000000-05:00</expiration_date>
    </coupons>
    <coupons diffgr:id="coupons3" msdata:rowOrder="2">
      <coupon_code>81BIN     </coupon_code>
      <discount_amount>10</discount_amount>
      <discount_type>1</discount_type>
      <expiration_date>2003-01-31T00:00:00.0000000-05:00</expiration_date>
    </coupons>
    <coupons diffgr:id="coupons4" msdata:rowOrder="3">
      <coupon_code>99GGY     </coupon_code>
      <discount_amount>5</discount_amount>
      <discount_type>0</discount_type>
      <expiration_date>2002-12-31T00:00:00.0000000-05:00</expiration_date>
    </coupons>
  </AngusHardware>
  <diffgr:before>
    <coupons diffgr:id="coupons1" msdata:rowOrder="0">
      <coupon_code>077GH     </coupon_code>
      <discount_amount>15</discount_amount>
      <discount_type>0</discount_type>
      <expiration_date>2002-11-09T14:01:24.1830000-05:00</expiration_date>
    </coupons>
  </diffgr:before>
</diffgr:diffgram>

This is the DiffGram, and it shows the current state ("after") of the data in the DataSet, as well as a "before" state in the diffgr:before element.

The DiffGram is an XML document that has three sections. The first, the current data instance, is represented by an XML element whose name matches the DataSet name; in this case, that's the AngusHardware element. Under the data instance element, the current state of each row in each of the DataSet's DataTables is serialized as a simple XML element.

The second section, diffgr:before, lists the values of any rows that have changed before the change. And the third section, diffgr:errors, shows any errors that occurred during the generation of the DiffGram.

Example 11-9 shows the general format of the DiffGram.

Example 11-9. The DiffGram format
<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr=
"urn:schemas-microsoft-com:xml-diffgram-v1">
  <DataSetName>
    <DataTableName diffgr:id="DataTableName
1" msdata:rowOrder="0" diffgr:hasChanges="modified">
      <DataColumnName>DataColumnValue
</DataColumnName>
      <DataColumnName>DataColumnValue
</DataColumnName>
      ...
    </DataTableName>
    <DataTableName diffgr:id="DataTableName
2" msdata:rowOrder="1">
      <DataColumnName>DataColumnValue
</DataColumnName>
      <DataColumnName>DataColumnValue
</DataColumnName>
      ...
    </DataTableName>
  </DataSetName>
  <diffgr:before>
    <DataTableName diffgr:id="DataTableName
n" msdata:rowOrder="DataRown">
      <DataColumnName>DataColumnValue
</DataColumnName>
      <DataColumnName>DataColumnValue
</DataColumnName>
      ...
    </DataTableName>
    ...
  </diffgr:before>
  <diffgr:errors>
    <DataTableName diffgr:id="DataTableName
n" diffgr:Error="ErrorText"/>
    ...
  </diffgr:errors> 
</diffgr:diffgram>

The following details the DiffGram elements, attributes, and content:


diffgr:diffgram

This is the root element of the DiffGram. It uses two namespaces, prefixed with msdata and diffgr, respectively.


DataSetName

This element's name is the name of the DataSet. All the current values of each DataTable's DataRows are included within this element.


DataTableName

This element's name is the name of the DataTable, and the element represents an individual DataRow.


DataColumnName

This element represents a DataColumn within a single DataRow. Its content is the current value of the column for that row.


diffgr:before

This is the element that contains the previous values of any changed DataRows. Its content represents the previous value of a DataRow instance with the matching value of the DataTableName's diffgr:id attribute.


diffgr:errors

This is the element that contains any error messages. Its diffgr:Error attribute contains the error message for the DataRow instance with the matching value of the DataTableName's diffgr:id attribute.


diffgr:id

This attribute represents a unique identifier for a DataRow. Its content is made up of the DataTable name and a sequential number. It is used to map the current value of a DataRow to the previous value in the diffgr:before section or to any errors in the diffgr:errors section.


msdata:rowOrder

This attribute indicates the order of the DataRow within the DataTable.


diffgr:hasChanges

This attribute is used to indicate whether the current value of a DataRow represents any changes, in which case the previous values will be listed in the diffgr:before section. diffgr:hasChanges can have the value inserted, modified, or descent:


inserted

The value inserted identifies an element which has been added


modified

The value modified identifies an element that has been modified


descent

The value descent identifies an element where one or more children from a parent-child relationship have been modified


diffgr:Error

This attribute's content is a textual error message describing an error that arose while attempting to change the data in a DataSet.

Although the DiffGram is used internally by .NET for remoting and web services, it can also be used by any external system that needs to communicate database changes to a .NET DataSet.

    [ Team LiB ] Previous Section Next Section