Making Modifications

for RuBoard

Obviously, to eventually update a data store with information, it must first be changed in the DataSet that a data adapter will use to synchronize with the data store. These changes can occur through explicit programmatic manipulation of the data by adding, modifying, and deleting data in a DataTable , using the Merge method of the DataSet , the ImportRow or LoadDataRow methods of the DataTable , the Add method of the DataRowCollection object, the Item and ItemArray properties, or the Delete method of the DataRow as you learned over the last two days. In addition, changes can be made implicitly through bound controls on Windows Forms or Web Forms. In the case of the former, the data in the DataSet is changed automatically by the control, whereas in the latter, some additional code must be written in the ASP.NET page.

In either case, the key concept you need to keep in mind is that because the DataSet is a truly disconnected data cache (remember, it doesn't keep track of where it got its data), any changes you make to it won't be reflected in the data store until you synchronize using an object such as a data adapter. This behavior is contrary to the way the Recordset object functioned by default in ADO 2.x. In this way, a DataSet is particularly useful for batch update scenarios.

Note

Modifying data in a DataSet and then using a data adapter isn't the only technique for making changes to a database. You certainly have the option of executing data modification statements (SQL, stored procedures, functions) using a command object provided by a .NET Data Provider such as OleDbCommand . You'll learn more about this approach on Day 10, "Working with Commands."


However, for a data adapter to take the appropriate action when passed a DataSet , it must know which rows were changed and how they were changed in addition to what the values in the rows were changed to and from.

Understanding Row States

To eventually know which rows should be synchronized with the data store, the DataSet must track the state of each row in each DataTable in its DataTableCollection . It does this using the RowState property of the DataRow object, which is always set to one of the values from the DataRowState enumeration, as shown in Table 5.1.

Table 5.1. Values of the DataRowState Enumeration
Value Description
Added The row has been added to the DataRowCollection of a DataTable and AcceptChanges hasn't yet been called.
Deleted The row has been deleted using the Delete method of the DataRow object. It can't subsequently be accessed without throwing a DeletedRowInaccessibleException .
Detached The row exists, but isn't part of a DataRowCollection either because it was never added to a DataTable or was removed using the Remove method of the DataRowCollection .
Modified One or more values in the row have been changed and AcceptChanges hasn't been called.
Unchanged The row hasn't been changed since the last time AcceptChanges was called.

To illustrate when the row states are set consider the C# populateBooks method shown in Listing 5.1.

Listing 5.1 Illustrating row states. This method populates a DataSet and then manipulates rows to show the values of the DataRowState enumeration.
 private void populateBooks(string connect)   {      SqlConnection con = new SqlConnection(connect);      SqlDataAdapter da = new SqlDataAdapter ("usp_GetTitles", con);      books = new DataSet("ComputeBooksTitles");      da.SelectCommand.CommandType = CommandType.StoredProcedure;      da.Fill(books, "Titles");      DataTable titles = books.Tables["Titles"];      // All rows are Unchanged      titles.Rows[0].BeginEdit();      titles.Rows[0]["Description"] = "This book is too long";      titles.Rows[0].EndEdit();      Console.WriteLine(titles.Rows[0].RowState.ToString());   //Modified      titles.Rows[0].Delete();      Console.WriteLine(titles.Rows[0].RowState.ToString());   //Deleted      DataRow title = titles.Rows[1];      titles.Rows.Remove(title);      Console.WriteLine(title.RowState.ToString());   //Detached      DataRow drNewTitle = titles.NewRow();  //Detached      titles.Rows.Add(drNewTitle);      Console.WriteLine(drNewTitle.RowState.ToString());   //Added      titles.AcceptChanges();  //All Unchanged } 
graphics/analysis.gif

In Listing 5.1, you'll notice that the Titles table of the DataSet is first populated from a data adapter and then referenced as titles . At this point, all the rows in titles have their RowState property set to Unchanged . The first row (0) is then modified before being deleted, changing its RowState from Unchanged to Modified to Deleted . Note, however, that if the RowState had been inspected before the call to EndEdit , it would have been Unchanged because embedding the change in BeginEdit and EndEdit methods defers the changes to the row until the EndEdit method is called. The second row is then removed from the DataRowCollection using the Remove method, causing its RowState to be set to Detached . Finally, a new row is added to titles , causing its RowState to be set to Added . When AcceptChanges is invoked, all the rows that have a RowState of Modified or Added will be set to Unchanged , and the Deleted and Detached rows will be permanently removed.

Tip

This listing shows a new row being added to the titles DataTable first using the NewRow method to create the row, and then using the Add method to add it to the DataTableCollection . However, the Add method will throw an exception if any columns in the table have their AllowDBNull property set to False and don't have a DefaultValue that can automatically generate a value.


As mentioned on Day 3, "Working with DataSets," the HasChanges method of the DataSet object returns a Boolean that can be used to quickly determine whether there have been any changes (row states of Modified , Added , and Deleted ) made to any rows in any of its tables. In fact, the HasChanges method is overloaded to filter based on one or more values from the DataRowState enumeration to check for specific types of changes. If the following line of code were inserted just above the call to AcceptChanges , it would return False because no rows with the Modified state would exist in the DataSet at that time:

 Console.WriteLine(books.HasChanges(DataRowState.Modified)); 

The previous paragraph implies that detached rows are handled differently from deleted ones, and in fact this is precisely the case. Simply put, a row that is detached doesn't register as a changed row to ADO.NET, and so a data adapter won't look at it when deciding which rows to delete in the data store. Further, the HasChanges and GetChanges methods of the DataSet and DataTable will ignore detached rows returning False if detached rows exist and not returning rows with a state of Detached , respectively.

In addition to their invisibility, detached rows and deleted rows can cause a RowNotInTableException or DeletedRowInaccessibleException to be thrown if you try to manipulate them. For example, calling the AcceptChanges , GetChildRows , GetParentRows , GetParentRow , RejectChanges , or SetParentRow methods of a DataRow that is detached or deleted will throw a RowNotInTableException . Trying to access the Item or ItemArray properties of a deleted row will cause a DeletedRowInaccessibleException . You'll also find that you can't access the columns of a row after it's been detached without causing an exception. However, this isn't the case with a row that's been newly instantiated and not yet added to a DataTable .

Understanding Row Versions

Marking a row as modified, deleted, or added, however, isn't enough to ensure that the data is properly updated in the underlying data store. The DataSet must also track the versions of values within the rows of a DataTable . This is accomplished behind the scenes in the table and tracked with the DataRowVersion enumeration, as shown in Table 5.2. Various versions of a row can then be made accessible using a DataView and its RowStateFilter property or directly through the Item property of the DataRow object.

Table 5.2. Values of the DataRowVersion Enumeration
Value Description
Current Represents the data that is currently in the row, including both changed data and unmodified data
Default Represents the data based on the setting of the RowState property of the row
Original Represents the data that was originally used to populate the row
Proposed Represents changes made to the data after BeginEdit was called and before EndEdit ”can still be undone if CancelEdit is called

One way you can think of the row versions listed in Table 5.2 is as three distinct copies ( Current , Original , Proposed ) of each row that a table has available to use, but that need not all be used simultaneously . Values within the copies can be manipulated directly by your code, and the DataSet also moves values between the copies as methods such as EndEdit , AcceptChanges , and RejectChanges are called. Although there are four values in the enumeration, actually only three copies are available because the Default value simply acts as a pointer to one of the three copies ”which copy depends on the value of the RowState property.

To illustrate this, consider the sequence of code based on Listing 5.1 shown in Listing 5.2.

Listing 5.2 Changing row data. This listing corresponds to Figure 5.1 to show how a DataSet tracks row versions.
 titles.Rows[0]["Description"] = "Too long"; titles.Rows[0].BeginEdit(); titles.Rows[0]["Description"] = "Way too long"; // See Figure 5.1 titles.Rows[0].EndEdit(); titles.AcceptChanges(); 
Figure 5.1. Row versions. This figure illustrates the state of the table at the commented line in Listing 5.2.

graphics/05fig01.gif

graphics/analysis.gif

In Listing 5.2, assume that row 0 was just populated in the DataSet by a data adapter and the value of the Description column was the string "A Great Book." As a result, its RowState would be set to Unchanged . At this point, the DataSet will have created an Original version of each row and a Current version of each row and all their values will be the same. The Default version will simply point to the Current version because the RowState is Unchanged . However, after the first line of code executes, the value of the Description column in the Current version will be changed to "Too Long" and the RowState will be set to Modified . The Default version will still point to the Current version because the modified value is in the Current version. When the BeginEdit method is called, a Proposed version of the row is created and is then populated with the value "Way Too Long." This is illustrated in Figure 5.1, which captures the state of the table directly before the EndEdit method is called.

As shown in Figure 5.1, at this point, the Default version points to the Proposed version and the RowState remains set to Modified because accessing the value of the column will bring back the value "Way Too Long," and the row was previously modified.

When the EndEdit method is called, the values from the Proposed version are copied into the Current version and the Proposed version is destroyed . The RowState remains as Modified . As you might expect, calling CancelEdit will simply destroy the Proposed version without making any changes. Finally, when the AcceptChanges method is called, the Current value is copied into the Original value and so they're once again the same. Calling RejectChanges instead would have copied the Original value back into the Current value. Both methods change the RowState back to Unchanged .

Although not shown in Listing 5.2, it follows that immediately after a row such as drNewTitle in Listing 5.1 is created, it has a Current version but not an Original version because the row wasn't originally retrieved from a data store. When the AcceptChanges method is called, either on the row directly on the table or on the DataSet after the row is added to the DataRowCollection , the Current version is copied to the Original and so both versions are available.

To access the values in the various versions, the Item property of the DataRow is overloaded to support a second argument that specifies the DataRowVersion to retrieve. For example, between the BeginEdit and EndEdit method calls in Listing 5.2, the following code could be inserted:

 //returns Great Book string descO = titles.Rows[0]["Description",DataRowVersion.Original].ToString(); //returns Too Long string descC = titles.Rows[0]["Description",DataRowVersion.Current].ToString(); //return Way Too Long string descP = titles.Rows[0]["Description",DataRowVersion.Proposed].ToString(); //return Way Too Long string descD = titles.Rows[0]["Description",DataRowVersion.Default].ToString(); 

From this snippet, you'll notice that the Default version simply points to the Proposed version as shown in Figure 5.1. It's also important to note the differences in syntax between VB and C# in this context. Because C# supports the concept of an indexer, you needn't reference the Item property explicitly. Simply enclosing the column name in brackets after the row number assumes that you want to access the property denoted as the indexer; in this case, Item . This construct allows access to a property in an array-like syntax. As shown several times yesterday , in VB, you would have to explicitly use the Item property like so:

 Dim descD As String = titles.Rows(0).Item(   "Description",DataRowVersion.Default).ToString() 

Of course, to know which versions of the row are available, you can use the HasVersion method of the DataRow class as shown in Table 4.2. This method accepts a value from the DataRowVersion enumeration and simply returns true if the version has been created for the row. For example, you could use the method to determine whether a row has both Current and Original versions and, if so, compare their primary keys to see whether they have been changed. The method in the following code snippet does just that and returns true or false when passed in the row to inspect and the name of primary key column:

 private Boolean hasKeyChanged(ref DataRow row, string pk) {    if (row.HasVersion(DataRowVersion.Current) &      row.HasVersion(DataRowVersion.Original))      {         if (row[pk,DataRowVersion.Current] != row[pk,DataRowVersion.Original])          {             return true;          }          else return false;      }      else return false; } 

Revisiting DataView s

Obviously, row states and row versions are closely connected because the changing of a row state implies the population of a row version in many cases. This is most clearly seen in the DataView object and its RowStateFilter property. As discussed on Day 3, the DataView object is used to view the rows of a DataTable in different ways. Although Day 3 focused primarily on changing the view based on sorting and filtering the various row versions, the RowStateFilter property actually can be used to see an entire collection of rows from one of the other versions through the DataViewRowState enumeration shown in Table 5.3.

Table 5.3. Values of the DataViewRowState Enumeration
Value Description
Added Allows only new rows ( Added state) to be visible ”displays their Current version
CurrentRows Shows the Current version of rows that include rows in the Unchanged , Added , and Modified states
Deleted Allows rows in the Deleted state to be visible (the Current version)
ModifiedCurrent Shows rows in the Current row version that are in the Modified row state
ModifiedOriginal Shows rows in the Original row version that are in the Modified row state
None No filter will be placed on the view
OriginalRows Shows all rows in the Original row version including both rows in the Unchanged and Deleted row states
Unchanged Shows the Current version of rows in the Unchanged state (which would be the same as the Original version)

As you can see from Table 5.3, values of the DataViewRowState enumeration encompass aspects of both the DataRowState and DataRowVersion enumerations to allow you to easily create a consistent view of the data in a DataTable . One use of this ability might be to present before-and-after snapshots of the data to a user for inspection before passing the changes to a data adapter for synchronizing with the data store, as in the showOriginal method in Listing 5.3.

Listing 5.3 Viewing row versions. This method binds the original data from a table in a DataSet to a grid using a DataView .
 public DataView showOriginal(ref DataSet ds, String table,   String sort, ref DataGrid grid) {   DataView myView = new DataView(ds.Tables[table],null,     sort,DataViewRowState.ModifiedOriginal);   grid.DataSource = myView;   grid.DataBind();   return myView; } 
graphics/analysis.gif

The showOriginal method in the previous code snippet first creates a new DataView of the table in the DataSet passed into the method. The DataView is sorted using the sort string also passed in, and will display only the original rows that have been modified using the ModifiedOriginal value of the DataRowViewState enumeration. The new DataView is then bound to the ASP.NET DataGrid control and returned from the method.

Retrieving Changes

As you learned on Day 3, the GetChanges method is exposed by both the DataSet and DataTable classes. Both methods are overloaded to accept one or more DataRowState values to return only those rows that match the row state. If no row state is passed, all rows with Modified , Deleted , and Added row states are returned. As you might expect, the return values from the GetChanges method for the DataSet and the DataTable differ in that they return a DataSet and DataTable , respectively.

The primary use of these methods in n-tiered applications is as shown on Day 3; namely, to extract a subset of rows and then pass those rows to a class that can synchronize them with the underlying data store. This technique is useful to minimize the amount of data that must be marshaled between logical tiers (and possibly between machines), and thereby increase performance.

Tip

As with other methods that accept an enumerated value, such as HasChanges , the GetChanges method can accept multiple values in a logical OR using the VB Or keyword or the C# operator. So, to create a new DataSet that contains only Detached and Unchanged rows, you could use the VB syntax ds.GetChanges(DataRowState.Detached Or DataRowState.Unchanged) or the C# syntax ds.GetChanges(DataRowState.Detached DataRowState.Unchanged); .


However, the GetChanges method in conjunction with accessing data in the various row versions can also be used to enumerate all the changes in a table before the AcceptChanges method is called. The code in Listing 5.4 illustrates this concept by showing a WriteXmlChanges method that creates an XML document that encapsulates all the changes to the passed-in DataTable . The method then returns an XmlTextReader that the calling code can use to navigate through the XML using a stream-based approach.

Listing 5.4 Inspecting a DataTable . This method creates an XML document that describes the changes in a DataTable using the GetChanges method.
 public XmlTextReader WriteXmlChanges(DataTable dt, String pk) {    MemoryStream s = new MemoryStream();    XmlTextWriter xmlChanges = new XmlTextWriter(s,      System.Text.Encoding.Default);   try   {      xmlChanges.Formatting = Formatting.Indented;      xmlChanges.Indentation = 4;      xmlChanges.WriteStartDocument();      xmlChanges.WriteComment("Changes for the " + dt.TableName + " table");      xmlChanges.WriteStartElement("diff",dt.TableName + "Diff",        "http://mycompany.org");      xmlChanges.WriteAttributeString("primaryKey",pk);      // Extract Deleted rows      DataTable del = dt.GetChanges(DataRowState.Deleted);      if (del  != null)      {        xmlChanges.WriteStartElement("Delete");        foreach (DataRow row in del.Rows)        {          xmlChanges.WriteStartElement("row");          xmlChanges.WriteString(row[pk,DataRowVersion.Original].ToString());          xmlChanges.WriteEndElement(); //finish the Row tag         }         xmlChanges.WriteEndElement(); //finish the Delete tag       }       // Extract Modified Rows       DataTable mod = dt.GetChanges(DataRowState.Modified);       if (mod  != null)       {           xmlChanges.WriteStartElement("Change");           foreach (DataRow row in mod.Rows)           {             xmlChanges.WriteStartElement("row");             xmlChanges.WriteAttributeString("primaryKey",row[pk,               DataRowVersion.Original].ToString());             foreach (DataColumn col in mod.Columns)               {                 if (row[col.ColumnName, DataRowVersion.Current].ToString()  !=                   row[col.ColumnName, DataRowVersion.Original].ToString())                 {                   xmlChanges.WriteStartElement(col.ColumnName);                   xmlChanges.WriteAttributeString("original",                     row[col.ColumnName,DataRowVersion.Original].ToString());                   xmlChanges.WriteString(row[col.ColumnName,                     DataRowVersion.Current].ToString());                   xmlChanges.WriteEndElement(); //finish column                 }               }               xmlChanges.WriteEndElement(); //finish the Row tag           }           xmlChanges.WriteEndElement(); //finish the Change tag       }       // Extract Added Rows       DataTable add = dt.GetChanges(DataRowState.Added);       if (add  != null)       {           xmlChanges.WriteStartElement("Add");           foreach (DataRow row in add.Rows)           {             xmlChanges.WriteStartElement("row");             foreach (DataColumn col in add.Columns)               {                 xmlChanges.WriteElementString(col.ColumnName,                   row[col.ColumnName, DataRowVersion.Current].ToString());               }               xmlChanges.WriteEndElement(); //finish the Row tag           }           xmlChanges.WriteEndElement(); //finish the Add tag       }       xmlChanges.WriteEndDocument();  //finish the document       xmlChanges.Flush();       s.Position = 0;       return new XmlTextReader(s);   }   catch (IOException e)   {        logError(e, dt);        return null;   }   catch (XmlException e)   {        logError(e, dt);        return null;   }   catch (Exception e)   {         logError(e, dt);         return null;   } } 
graphics/analysis.gif

Although Listing 5.4 is fairly long, it can be broken into five distinct sections. The first section instantiates the MemoryStream that will be used to hold the XML document as well as the XmlTextWriter used to write to the MemoryStream . Directly inside the try block, the XmlTextWriter sets up the properties that determine how the document will be formatted as well as writes the root element with an attribute that identifies the primary key and a comment to the stream.

The second section calls the GetChanges method of the DataTable passed to the method to extract only those rows that have been Deleted . If rows are returned, a Delete element is written to the XML document with the WriteStartElement method and each row is written as a Row element inside. The point to note here is that in order to retrieve the values from the deleted row, you must access the Original version of the row. In this case, the primary key column, passed in the pk argument, is the only column written to the XML document because the primary key is all that is required to delete a row.

Note

The WriteXmlChanges method as shown here would work only with a single-column primary key. It could easily be rewritten to accept an array of strings to represent possible composite keys.


Framework Patterns

This example uses classes from the System.Xml and System.IO namespaces to write out the XML stream and return an object that can be used to stream through the document. The classes in the System.Xml namespace, XmlTextWriter and XmlTextReader , implement a programming model that is different from both the Document Object Model (DOM) and the Simple API for XML (SAX) programming models exposed in the Microsoft XML (MSXML) parser familiar to COM developers. The advantage to using this approach that melds DOM and SAX is twofold. First, like SAX, it doesn't incur the memory overhead of DOM where the entire document is parsed into a tree structure before it's available for inspection. Second, like the DOM, it implements a pull rather than a push model, which allows developers to use a familiar cursor-style looping construct rather than having to respond to events fired from the parser.

As you'll find throughout the .NET Framework classes, the System.Xml classes rely on stream classes from the System.IO namespace to provide the underlying stream of bytes through which to work. In this case, a MemoryStream object is used to represent a stream of bytes stored in memory. Its base class, Stream , also acts as the base class for other stream objects in the Framework, such as FileStream and NetworkStream , that have different backing stores and that can be used polymorphically where a Stream object is called for.

The third section is analogous to the second by retrieving all the modified rows and looping through them, writing a row element for each one. However, inside the loop sits another nested loop that iterates the columns in the row in order to write out both the old and new values to the XML document. Note that the Current and Original versions are used to find the new and old values, respectively, and that only columns that differ between versions in their string representations are written to the stream. In this case, the original value is written as an attribute using the WriteAttributeString method.

The fourth section uses GetChanges to retrieve the newly inserted rows defined as those with a row state of Added . Like the previous section, it loops through all the rows and each column in order to write all the values to the XML stream. Note that, in this case, the Current version represents the new values because added rows don't yet have an Original version.

Finally, the ending element is written to the document and the Position of the stream is reset to the beginning. Because the method returns an XmlTextReader , a new object is instantiated and passed the MemoryStream . Most of the code is wrapped in a try catch block in order to handle any errors. In this case, possible exceptions include IOException , XmlException , and various exceptions from the System.Data namespace that are all caught under the generic catch (Exception e) block. In all cases, the exception is simply passed to a method to log the error.

The WriteXmlChanges method can then be called like so:

 XmlTextReader tr = WriteXmlChanges(titles.GetChanges(),"ISBN"); 

Note that the default version of GetChanges is used to pass only rows that have been changed to the method. The result is an XML document that can be read from the XmlTextReader with the structure shown in Listing 5.5.

Listing 5.5 XML changes. This listing shows the output from the WriteXmlChanges method shown in Listing 5.4.
 <?xml version="1.0" encoding="Windows-1252"?> <!--Changes for the Titles table--> <diff:TitlesDiff primaryKey="ISBN" xmlns:diff="http://mycompany.org">     <Delete>         <row>06720006X </row>     </Delete>     <Change>         <row primaryKey="06720001X">             <Description original="Great Book">Way too long</Description>         </row>     </Change>     <Add>         <row>             <ISBN>077802000X</ISBN>             <Title>Teach Yourself Enterprise ADO.NET in 21 Days</Title>             <Description />             <Author>Fox, Dan</Author>             <PubDate>4/15/2001 12:00:00 AM</PubDate>             <Price>31.99</Price>             <Discount />             <BulkDiscount />             <BulkAmount />             <Cover />             <CatID />             <Publisher>Sams</Publisher>         </row>     </Add> </diff:TitlesDiff> 
graphics/analysis.gif

From Listing 5.5, you can see that one row was deleted from the table with the ISBN 06720006X, one row's Description column was modified, and one new row was added.

Obviously, creating an XML document like the one shown in Listing 5.5 isn't something you would do everyday. After all, remember it is the job of the data adapter to look for changed rows much like the code in Listing 5.4 and apply those changes to the data store. However, as you'll learn on Day 14, "Working with Other Providers," there are rare occasions when you might want to write your own .NET Data Provider to work with a proprietary data store or one that's not easily accessible via ODBC or OLE DB. As a result, you might implement an XML message-passing scheme where an XML document is used to notify the data store of changes. In fact, the code shown in Listing 5.4 might be the basis for the Update method of a custom data adapter class that implements the IDataAdapter interface.

In any case, it should come as no surprise that the DataSet already implements functionality similar to that shown in this section.

DiffGrams
graphics/newterm.gif

It turns out that the DataSet also supports an intrinsic method of creating an XML document to represent the changes in its tables, referred to as a DiffGram. DiffGrams were first introduced as a Web update to SQL Server 2000 that extended SQL Server's XML processing capabilities by allowing it to modify tables based on the contents of an XML document that conformed to the DiffGram schema.

To create a DiffGram, you can use the WriteXml method of the DataSet object (which you'll learn more about on Day 7, " DataSet s and XML"). Simply put, the WriteXml method writes the contents of the DataSet in XML format to the stream you specify. An overloaded version accepts a value of the XmlWriteMode enumeration as the second argument that specifies what is to be written. By passing the DiffGram value as the second argument, an XML document that shows before-and-after versions of modified rows and new rows is created. The following code is used to generate the DiffGram:

 MemoryStream s = new MemoryStream(); DataSet dsMod = books.GetChanges(); dsMod.WriteXml(s,XmlWriteMode.DiffGram); s.Position = 0; XmlTextReader xmlTr = new XmlTextReader(s); 

Here, a MemoryStream is also used to store the XML document and a new DataSet is first created to hold only the changed data. A DiffGram is then written to the stream with the WriteXml method, and its position is reset to before instantiating the XmlTextReader that will be used to read the document. The resulting XML document that incorporates the same changes as the one in Listing 5.4 can be seen in Listing 5.6.

Listing 5.6 A DiffGram. This listing shows an XML DiffGram produced by the WriteXml method.
 <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"   xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">   <ComputeBooksTitles>     <Titles diffgr:id="Titles1" msdata:rowOrder="0"       diffgr:hasChanges="modified>       <ISBN>06720001X </ISBN>       <Title>ADO 2.0 Programmers Reference</Title>       <Description>Way too long</Description>       <Author>Sussman, David/Homer, Alex</Author>       <PubDate>1998-10-01T00:00:00.0000000-05:00</PubDate>       <Price>29.99</Price>       <Discount>9.5687</Discount>       <BulkDiscount>10</BulkDiscount>       <BulkAmount>50</BulkAmount>       <CatID>21b60927-5659-4ad4-a036-ab478d73e754</CatID>     </Titles>     <Titles diffgr:id="Titles3" msdata:rowOrder="2"       diffgr:hasChanges="inserted>       <ISBN>077802000X</ISBN>       <Title>Teach Yourself Enterprise ADO.NET in 21 Days</Title>       <Author>Fox, Dan</Author>       <PubDate>2001-04-15T00:00:00.0000000-05:00</PubDate>       <Price>31.99</Price>       <Publisher>Sams</Publisher>     </Titles>   </ComputeBooksTitles>   <diffgr:before>     <Titles diffgr:id="Titles1" msdata:rowOrder="0">       <ISBN>06720001X </ISBN>       <Title>ADO 2.0 Programmers Reference</Title>       <Description>Great Book</Description>       <Author>Sussman, David/Homer, Alex</Author>       <PubDate>1998-10-01T00:00:00.0000000-05:00</PubDate>       <Price>29.99</Price>       <Discount>9.5687</Discount>       <BulkDiscount>10</BulkDiscount>       <BulkAmount>50</BulkAmount>       <CatID>21b60927-5659-4ad4-a036-ab478d73e754</CatID>     </Titles>     <Titles diffgr:id="Titles2" msdata:rowOrder="1">       <ISBN>06720006X </ISBN>       <Title>Advanced MS Visual Basic 6/+CD/2nd edition</Title>       <Author>Mandelbrot Set International Ltd</Author>       <PubDate>1998-10-01T00:00:00.0000000-05:00</PubDate>       <Price>59.99</Price>       <Discount>9.57</Discount>       <BulkDiscount>20</BulkDiscount>       <BulkAmount>75</BulkAmount>       <CatID>21b60927-5659-4ad4-a036-ab478d73e754</CatID>       <Publisher>Msft </Publisher>     </Titles>   </diffgr:before> </diffgr:diffgram> 

The key point that should strike you when comparing Listing 5.5 with Listing 5.6 is that the DiffGram schema includes complete representations of the before-and-after states of a modified row and includes the before views in the diffgr:before tag and the after views simply inside the root element. Modified rows have their hasChanges attribute set to "modified," whereas new rows have theirs set to "inserted." Note also that deleted rows simply appear in the diffgr:before element and, as you would expect, have no after representation.

Obviously, you can also capitalize on the DiffGram format if you need to pass XML to update a data store.

for RuBoard


Sams Teach Yourself Ado. Net in 21 Days
Sams Teach Yourself ADO.NET in 21 Days
ISBN: 0672323869
EAN: 2147483647
Year: 2002
Pages: 158
Authors: Dan Fox

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net