Figure 12.1: The Relationships tab for FK_Orders_Customers The Cascade Update Related Fields check box indicates whether a change to a value in the primary key column of the primary key
Trang 1Issues Involved When Updating the Primary Key of a Parent Row
In this section, you'll learn about the issues involved when attempting to update the
primary key in a parent DataTable, and then pushing the update to the underlying
database table The issues occur when the child database table already contains rows that use the primary key you want to change in the parent table
The examples in this section will use the Customers and Orders table, which are related through the foreign key on the CustomerID column of the Orders table to the CustomerID column of the Customers table
As you'll learn, you're much better off not allowing changes to the primary key column of
a table If you allow changes to the primary key column, then as you'll see shortly, you can run into problems when pushing the change to the database Instead, you should set the ReadOnly property to true for the primary key DataColumn in your parent DataTable, and also set ReadOnly to true for the foreign key DataColumn in your child DataTable That prevents changes to the values in these DataColumn objects
If you really need to change the primary key and foreign key values, you should delete
and then recreate the rows in the database with the new primary key and foreign key values
You can control how updates and deletes are performed using the properties of the
foreign key in the SQL Server database and also the UpdateRule and DeleteRule
properties of a ForeignKeyConstraint object You'll explore both of these items in the following sections
Controlling Updates and Deletes Using SQL Server
You can control how updates and deletes are performed using SQL Server by setting the properties of the foreign key You set these properties using the Relationships tab of a database table's Properties dialog box You open this dialog box in Enterprise Manager for the Orders table by performing the following steps:
1 Right-click the Orders table in the Tables node of Enterprise Manager
2 Select Design Table from the pop-up list
3 Press the Manage Relationships button in the toolbar of the Design Table dialog box
4 Select the foreign key you want to examine in the Select relationship drop-down list
Trang 2Figure 12.1 shows the Relationships tab for the foreign key named
FK_Orders_Customers that contains the details of the foreign key between the Orders and Customers tables As you can see, these two tables are related through a foreign key
on the CustomerID column
Figure 12.1: The Relationships tab for FK_Orders_Customers
The Cascade Update Related Fields check box indicates whether a change to a value in the primary key column of the primary key table (the parent table) is also made to the foreign key column of the corresponding rows of the foreign key table (the child table) For example, assume this box is checked and you changed the CustomerID in the row of the Customers table from ALFKI to ANATR; this would also cause the CustomerID column to change from ALFKI to ANATR in the rows of the Orders table
Similarly, the Cascade Delete Related Records check box indicates whether deleting a row in the primary key table also deletes any related rows from the foreign key table For example, assume this box is checked and you deleted the row with the CustomerID of ANTON from the Customers table; this would cause the rows with the CustomerID of ANTON to also be deleted from the Orders table
Note Typically, you should leave both check boxes in their default unchecked state If you check them, the database will make changes to the rows in the child table behind the scenes and as you'll see shortly, you'll run into problems when pushing
changes from your DataSet to the database
Controlling Updates and Deletes Using the UpdateRule and DeleteRule Properties of
a ForeignKeyConstraint Object
Trang 3You can also control updates and deletes using the UpdateRule and DeleteRule properties
of a ForeignKeyConstraint object These properties are of the System.Data.Rule
enumeration type; members of this type are shown in Table 12.4
Table 12.4: Rule ENUMERATION MEMBERS
CONSTANT DESCRIPTION
Cascade Indicates that the delete or update to the DataRow objects in the parent
DataTable are also made in the child DataTable This is the default
SetDefault Indicates that the DataColumn values in the child DataTable are to be set
to the value in the DefaultValue property of the DataColumn
SetNull Indicates that the DataColumn values in the child DataTable are to be set
to DBNull
By default, UpdateRule is set to Cascade; therefore, when you change the DataColumn in the parent DataTable on which the ForeignKeyConstraint was created, then the same change is also made in any corresponding DataRow objects in the child DataTable You should set UpdateRule to None in your program; otherwise, as you'll learn in the next
database
By default, DeleteRule is set to Cascade; therefore, when you delete a DataRow in the parent DataTable, any corresponding DataRow objects in the child DataTable are also deleted This is fine, as long as you remember to push the deletes to the child table before you push the deletes to the parent table
Updating the Primary Key of a Parent Table and Pushing the Change to the
Database
In this section you'll learn what happens if you attempt to update the primary key in a parent table when there are corresponding rows in the child table Assume the following:
• There is a row in the Customers table with a CustomerID of J6COM A copy of this row is stored in a DataTable named customersDT
• There is a row in the Orders table that also has a CustomerID of J6COM A copy
of this row is stored in a DataTable named ordersDT
• The customersDT and ordersDT DataTable objects are related to each other using
the following DataRelation:
• new DataRelation(
• "CustomersOrders",
Trang 4• customersDT.Columns["CustomerID"],
• ordersDT.Columns["CustomerID"]
• );
• customersOrdersDataRel
• );
Now, the two settings for the Cascade Update Related Fields check box for
FK_Orders_Customers are
• Unchecked, meaning that changes to the CustomerID primary key value in the
Customers table are not cascaded to the Orders table This is the default
• Checked, meaning that changes to the CustomerID primary key value in the
Customers table are cascaded to the Orders table
In addition, the settings of interest for the UpdateRule property of the
ForeignKeyConstraint object added when the earlier DataRelation was created are
are cascaded to ordersDT This is the default
not cascaded to ordersDT
Let's examine the three most important cases that vary the checking of the Cascade Update Related Fields box and setting of the UpdateRule property to Cascade and then None
Note You can use the ModifyingRelatedData2.cs program as the basis for trying out the
three cases described in this section
First Case
Assume the following:
• Cascade Update Related Fields box is checked
• UpdateRule is set to Cascade
If you change to the CustomerID DataColumn from J6COM to J7COM and push the change to the database, then the change is made successfully in the customersDT and ordersDT DataTable objects and also in the Customers and Orders database tables This works as long as you use only the OrderID column in the WHERE clause of the Command object in the UpdateCommand property of your DataAdapter For example:
Trang 5ordersUpdateCommand.CommandText =
"UPDATE Orders " +
"SET " +
" CustomerID = @NewCustomerID " +
"WHERE OrderID = @OldOrderID";
This UPDATE uses "last one wins" concurrency since only the OrderID primary key
column is used in the WHERE clause (the old CustomerID column is left out of the WHERE clause) As mentioned in the previous chapter, "last one wins" concurrency is bad because one user might overwrite a change made by another user
If instead you also include the old CustomerID column value in the WHERE clause of the UPDATE, as shown in the following example,
ordersUpdateCommand.CommandText =
"UPDATE Orders " +
"SET " +
" CustomerID = @NewCustomerID " +
"WHERE OrderID = @OldOrderID " +
"AND CustomerID = @OldCustomerID";
then pushing the change to the database would fail because the original row in the orders table wouldn't be found The original row wouldn't be found since the CustomerID has already been changed from J6COM to J7COM in the Orders table automatically by the database because Cascade Update Related Fields is checked for the foreign key in the
Orders table, but in ordersDT the old CustomerID is set to J6COM Therefore, the
addition of OrderID = @OldOrderID in the WHERE clause prevents the row from being found Instead, the UPDATE causes a DBConcurrencyException to be thrown
Second Case
Assume the following:
• Cascade Update Related Fields is unchecked
• UpdateRule is set to Cascade
property of the DataAdapter is set as follows:
• "UPDATE Orders " +
• "SET " +
• " CustomerID = @NewCustomerID " +
• "WHERE OrderID = @OldOrderID";
Trang 6If you change the CustomerID from J6COM to J7COM in customersDT and push the change to the database, then the UPDATE will throw a SqlException This is because the child Orders table currently contains a row with the CustomerID of J6COM, and because
of the foreign key you can't change the CustomerID in the parent Customers table Even
if you tried to change the CustomerID in ordersDT first and attempted to push the change
to the database, you'd run into the same exception
Third Case
Assume the following:
• Cascade Update Related Fields is unchecked
• UpdateRule is set to None
DataAdapter is the same as in the second case
The following code sets the UpdateRule of the ChildKeyConstraint to None:
myDataSet.Relations["CustomersOrders"].ChildKeyConstraint.UpdateRule =
Rule.None;
If you try to change the CustomerID from J6COM to J7COM in customersDT, then you'll throw an InvalidConstraintException This is because the child ordersDT DataTable currently contains a DataRow with the CustomerID of J6COM, and because of the
foreign key you can't change the CustomerID in the parent customersDT DataTable Even if you tried to change the CustomerID in ordersDT first, you'd run into the same exception
Conclusion
The enforcement of the constraints in the previous three examples is correct, and they show what a headache changing the primary key column values can be The first case is the only one that works, and even then you have to resort to using "last one wins"
concurrency in the UPDATE statement, which you should typically avoid
What do you do if you want to change the primary key column value and apply the same change to the child rows? The easiest way is to simply delete the rows in the child table first, change the primary key value in the parent table, and recreate the rows in the child table with the new primary key value