[ Team LiB ] |
Recipe 4.10 Updating a DataSet with a Many-to-Many RelationshipProblemYou have a DataSet that contains two tables that have a many-to-many relationship between them using a third junction table. You get referential integrity errors when you try to update changes to the data in this DataSet back to the data source. You need to do this successfully. SolutionUse the techniques described in the discussion. The schema of table TBL0410Parent used in this solution is shown in Table 4-5.
The schema of table TBL0410Child used in this solution is shown in Table 4-6.
The schema of table TBL0410ParentChild used in this solution is shown in Table 4-7.
The solution uses eleven stored procedures described in Table 4-8.
The sample code contains five event handlers and four methods:
The 11 stored procedures used in this example are shown in Example 4-13 through Example 4-23. Example 4-13. Stored procedure: SP0410_DeleteChildCREATE PROCEDURE SP0410_DeleteChild @ChildId int AS SET NOCOUNT ON delete from TBL0410Child where ChildId=@ChildId return 0 Example 4-14. Stored procedure: SP0410_DeleteParentCREATE PROCEDURE SP0410_DeleteParent @ParentId int AS SET NOCOUNT ON delete from TBL0410Parent where ParentId=@ParentId return 0 Example 4-15. Stored procedure: SP0410_DeleteParentChildCREATE PROCEDURE SP0410_DeleteParentChild @ParentId int, @ChildId int AS SET NOCOUNT ON delete from TBL0410ParentChild where ParentId=@ParentId and ChildId=@ChildId return 0 Example 4-16. Stored procedure: SP0410_GetChildCREATE PROCEDURE SP0410_GetChild @ChildId int=null AS SET NOCOUNT ON if @ChildId is not null begin select ChildID, Field3, Field4 from TBL0410Child where ChildId=@ChildId return 0 end select ChildId, Field3, Field4 from TBL0410Child return 0 Example 4-17. Stored procedure: SP0410_GetParentCREATE PROCEDURE SP0410_GetParent @ParentId int=null AS SET NOCOUNT ON if @ParentId is not null begin select ParentId, Field1, Field2 from TBL0410Parent where ParentId=@ParentId return 0 end select ParentId, Field1, Field2 from TBL0410Parent return 0 Example 4-18. Stored procedure: SP0410_GetParentChildCREATE PROCEDURE SP0410_GetParentChild @ParentId int=null AS if @ParentId is not null begin select ParentId, ChildID from TBL0410ParentChild where ParentId=@ParentId return 0 end select ParentId, ChildID from TBL0410ParentChild return 0 Example 4-19. Stored procedure: SP0410_InsertChildCREATE PROCEDURE SP0410_InsertChild @ChildId int output, @Field3 nvarchar(50)=null, @Field4 nvarchar(50)=null AS SET NOCOUNT ON insert TBL0410Child( Field3, Field4) values ( @Field3, @Field4) if @@rowcount=0 return 1 set @ChildId=Scope_Identity( ) select @ChildId ChildId return 0 Example 4-20. Stored procedure: SP0410_InsertParentCREATE PROCEDURE SP0410_InsertParent @ParentId int output, @Field1 nvarchar(50)=null, @Field2 nvarchar(50)=null AS SET NOCOUNT ON insert TBL0410Parent( Field1, Field2) values ( @Field1, @Field2) if @@rowcount=0 return 1 set @ParentId=Scope_Identity( ) select @ParentId ParentId return 0 Example 4-21. Stored procedure: SP0410_InsertParentChildCREATE PROCEDURE SP0410_InsertParentChild @ParentId int, @ChildId int AS SET NOCOUNT ON insert TBL0410ParentChild( ParentId, ChildId) values ( @ParentId, @ChildId) if @@rowcount=0 return 1 return 0 Example 4-22. Stored procedure: SP0410_UpdateChildCREATE PROCEDURE SP0410_UpdateChild @ChildId int, @Field3 nvarchar(50)=null, @Field4 nvarchar(50)=null AS SET NOCOUNT ON update TBL0410Child set Field3=@Field3, Field4=@Field4 where ChildId=@ChildId if @@rowcount=0 return 1 return 0 Example 4-23. Stored procedure: SP0410_UpdateParentCREATE PROCEDURE SP0410_UpdateParent @ParentId int, @Field1 nvarchar(50)=null, @Field2 nvarchar(50)=null AS SET NOCOUNT ON update TBL0410Parent set Field1=@Field1, Field2=@Field2 where ParentId=@ParentId if @@rowcount=0 return 1 return 0 The C# code for updating a DataSet with a many-to-one relationship is shown in Example 4-24. Example 4-24. File: UpdateManyToManyRelationshipForm.cs// Namespaces, variables, and constants using System; using System.Configuration; using System.Windows.Forms; using System.Data; using System.Data.SqlClient; private DataSet ds; private SqlDataAdapter daParent, daParentChild, daChild; private const String PARENTTABLENAME = "TBL0410Parent"; private const String PARENTCHILDTABLENAME = "TBL0410ParentChild"; private const String CHILDTABLENAME = "TBL0410Child"; // Table column name constants for Parent table private const String PARENTID_FIELD = "ParentId"; private const String FIELD1_FIELD = "Field1"; private const String FIELD2_FIELD = "Field2"; // Table column parameter name constants for Child table private const String CHILDID_FIELD = "ChildId"; private const String FIELD3_FIELD = "Field3"; private const String FIELD4_FIELD = "Field4"; // Stored procedure name constants private const String DELETEPARENT_SP = "SP0410_DeleteParent"; private const String GETPARENT_SP = "SP0410_GetParent"; private const String INSERTPARENT_SP = "SP0410_InsertParent"; private const String UPDATEPARENT_SP = "SP0410_UpdateParent"; private const String DELETEPARENTCHILD_SP = "SP0410_DeleteParentChild"; private const String GETPARENTCHILD_SP = "SP0410_GetParentChild"; private const String INSERTPARENTCHILD_SP = "SP0410_InsertParentChild"; private const String DELETECHILD_SP = "SP0410_DeleteChild"; private const String GETCHILD_SP = "SP0410_GetChild"; private const String INSERTCHILD_SP = "SP0410_InsertChild"; private const String UPDATECHILD_SP = "SP0410_UpdateChild"; // stored procedure parameter name constants for Parent table private const String PARENTID_PARM = "@ParentId"; private const String FIELD1_PARM = "@Field1"; private const String FIELD2_PARM = "@Field2"; // Stored procedure parameter name constants for Child table private const String CHILDID_PARM = "@ChildId"; private const String FIELD3_PARM = "@Field3"; private const String FIELD4_PARM = "@Field4"; // . . . private void UpdateManyToManyRelationshipForm_Load(object sender, System.EventArgs e) { DataColumnCollection cols; DataColumn col; // Build the parent table. DataTable parentTable = new DataTable(PARENTTABLENAME); cols = parentTable.Columns; col = cols.Add(PARENTID_FIELD, typeof(Int32)); col.AutoIncrement = true; col.AutoIncrementSeed = -1; col.AutoIncrementStep = -1; cols.Add(FIELD1_FIELD, typeof(String)).MaxLength = 50; cols.Add(FIELD2_FIELD, typeof(String)).MaxLength = 50; // Build the ParentChild table. DataTable parentChildTable = new DataTable(PARENTCHILDTABLENAME); cols = parentChildTable.Columns; cols.Add(PARENTID_FIELD, typeof(Int32)).AllowDBNull = false; cols.Add(CHILDID_FIELD, typeof(Int32)).AllowDBNull = false; parentChildTable.PrimaryKey = new DataColumn[] {cols[PARENTID_FIELD], cols[CHILDID_FIELD]}; // Build the child table. DataTable childTable = new DataTable(CHILDTABLENAME); cols = childTable.Columns; col = cols.Add(CHILDID_FIELD, typeof(Int32)); col.AutoIncrement = true; col.AutoIncrementSeed = -1; col.AutoIncrementStep = -1; cols.Add(FIELD3_FIELD, typeof(String)).MaxLength = 50; cols.Add(FIELD4_FIELD, typeof(String)).MaxLength = 50; // Add the tables to the DataSet and create the relationship. ds = new DataSet( ); ds.Tables.Add(parentTable); ds.Tables.Add(parentChildTable); ds.Tables.Add(childTable); ds.Relations.Add(new DataRelation("Parent_ParentChild", parentTable.Columns[PARENTID_FIELD], parentChildTable.Columns[PARENTID_FIELD], true)); ds.Relations.Add(new DataRelation("Child_ParentChild", childTable.Columns[CHILDID_FIELD], parentChildTable.Columns[CHILDID_FIELD], true)); // Create the Parent DataAdapter. daParent = new SqlDataAdapter(GETPARENT_SP, ConfigurationSettings.AppSettings["Sql_ConnectString"]); daParent.SelectCommand.CommandType = CommandType.StoredProcedure; // Build the parent delete command. SqlCommand deleteCommand = new SqlCommand(DELETEPARENT_SP, daParent.SelectCommand.Connection); deleteCommand.CommandType = CommandType.StoredProcedure; deleteCommand.Parameters.Add(PARENTID_PARM, SqlDbType.Int, 0, PARENTID_FIELD); daParent.DeleteCommand = deleteCommand; // Build the parent insert command. SqlCommand insertCommand = new SqlCommand(INSERTPARENT_SP, daParent.SelectCommand.Connection); insertCommand.CommandType = CommandType.StoredProcedure; insertCommand.Parameters.Add(PARENTID_PARM, SqlDbType.Int, 0, PARENTID_FIELD); insertCommand.Parameters.Add(FIELD1_PARM, SqlDbType.NVarChar, 50, FIELD1_FIELD); insertCommand.Parameters.Add(FIELD2_PARM, SqlDbType.NVarChar, 50, FIELD2_FIELD); daParent.InsertCommand = insertCommand; // Build the parent update command. SqlCommand updateCommand = new SqlCommand(UPDATEPARENT_SP, daParent.SelectCommand.Connection); updateCommand.CommandType = CommandType.StoredProcedure; updateCommand.Parameters.Add(PARENTID_PARM, SqlDbType.Int, 0, PARENTID_FIELD); updateCommand.Parameters.Add(FIELD1_PARM, SqlDbType.NVarChar, 50, FIELD1_FIELD); updateCommand.Parameters.Add(FIELD2_PARM, SqlDbType.NVarChar, 50, FIELD2_FIELD); daParent.UpdateCommand = updateCommand; // Create the Child DataAdapter. daChild = new SqlDataAdapter(GETCHILD_SP, ConfigurationSettings.AppSettings["Sql_ConnectString"]); daChild.SelectCommand.CommandType = CommandType.StoredProcedure; // Build the child delete command. deleteCommand = new SqlCommand(DELETECHILD_SP, daChild.SelectCommand.Connection); deleteCommand.CommandType = CommandType.StoredProcedure; deleteCommand.Parameters.Add(CHILDID_PARM, SqlDbType.Int, 0, CHILDID_FIELD); daChild.DeleteCommand = deleteCommand; // Build the child insert command. insertCommand = new SqlCommand(INSERTCHILD_SP, daChild.SelectCommand.Connection); insertCommand.CommandType = CommandType.StoredProcedure; insertCommand.Parameters.Add(CHILDID_PARM, SqlDbType.Int, 0, CHILDID_FIELD); insertCommand.Parameters.Add(FIELD3_PARM, SqlDbType.NVarChar, 50, FIELD3_FIELD); insertCommand.Parameters.Add(FIELD4_PARM, SqlDbType.NVarChar, 50, FIELD4_FIELD); daChild.InsertCommand = insertCommand; // Build the child update command. updateCommand = new SqlCommand(UPDATECHILD_SP, daChild.SelectCommand.Connection); updateCommand.CommandType = CommandType.StoredProcedure; updateCommand.Parameters.Add(CHILDID_PARM, SqlDbType.Int, 0, CHILDID_FIELD); updateCommand.Parameters.Add(FIELD3_PARM, SqlDbType.NVarChar, 50, FIELD3_FIELD); updateCommand.Parameters.Add(FIELD4_PARM, SqlDbType.NVarChar, 50, FIELD4_FIELD); daChild.UpdateCommand = updateCommand; // Create the ParentChild DataAdapter. daParentChild = new SqlDataAdapter(GETPARENTCHILD_SP, ConfigurationSettings.AppSettings["Sql_ConnectString"]); daParentChild.SelectCommand.CommandType = CommandType.StoredProcedure; // Build the ParentChild delete command. deleteCommand = new SqlCommand(DELETEPARENTCHILD_SP, daParentChild.SelectCommand.Connection); deleteCommand.CommandType = CommandType.StoredProcedure; deleteCommand.Parameters.Add(PARENTID_PARM, SqlDbType.Int, 0, PARENTID_FIELD); deleteCommand.Parameters.Add(CHILDID_PARM, SqlDbType.Int, 0, CHILDID_FIELD); daParentChild.DeleteCommand = deleteCommand; // Build the ParentChild insert command. insertCommand = new SqlCommand(INSERTPARENTCHILD_SP, daParentChild.SelectCommand.Connection); insertCommand.CommandType = CommandType.StoredProcedure; insertCommand.Parameters.Add(PARENTID_PARM, SqlDbType.Int, 0, PARENTID_FIELD); insertCommand.Parameters.Add(CHILDID_PARM, SqlDbType.Int, 0, CHILDID_FIELD); daParentChild.InsertCommand = insertCommand; LoadData( ); dataGridParent.DataSource = parentTable.DefaultView; dataGridChild.DataSource = childTable.DefaultView; } private void LoadData( ) { // Fill the dataset. daParent.Fill(ds, PARENTTABLENAME); daChild.Fill(ds, CHILDTABLENAME); daParentChild.Fill(ds, PARENTCHILDTABLENAME); } private void CreateData(int parentRows, int childRows) { // Create some data update the data source with it. for(int iParent = 0; iParent < parentRows; iParent++) { DataRow parentRow = ds.Tables[PARENTTABLENAME].NewRow( ); parentRow[FIELD1_FIELD] = Guid.NewGuid().ToString( ); parentRow[FIELD2_FIELD] = Guid.NewGuid().ToString( ); ds.Tables[PARENTTABLENAME].Rows.Add(parentRow); } for(int iChild = 0; iChild < childRows; iChild++) { DataRow childRow = ds.Tables[CHILDTABLENAME].NewRow( ); childRow[FIELD3_FIELD] = Guid.NewGuid().ToString( ); childRow[FIELD4_FIELD] = Guid.NewGuid().ToString( ); ds.Tables[CHILDTABLENAME].Rows.Add(childRow); } // Randomly create the parent-child relationships. Random r = new Random((int)DateTime.Now.Ticks); foreach(DataRow rowParent in ds.Tables[PARENTTABLENAME].Rows) { if(rowParent.RowState != DataRowState.Deleted) { foreach(DataRow rowChild in ds.Tables[CHILDTABLENAME].Rows) { if(rowChild.RowState != DataRowState.Deleted && r.Next(2) == 1) { // Check to see that row doesn't exist // before adding. if(ds.Tables[PARENTCHILDTABLENAME]. Rows.Find(new object[] {rowParent[PARENTID_FIELD], rowChild[CHILDID_FIELD]}) == null) { ds.Tables[PARENTCHILDTABLENAME]. Rows.Add(new object[] {rowParent[PARENTID_FIELD], rowChild[CHILDID_FIELD]}); } } } } } } private void UpdateData( ) { try { daParentChild.Update(ds.Tables[PARENTCHILDTABLENAME].Select( null, null, DataViewRowState.Deleted)); daChild.Update(ds.Tables[CHILDTABLENAME].Select( null, null, DataViewRowState.Deleted)); daParent.Update(ds.Tables[PARENTTABLENAME].Select( null, null, DataViewRowState.Deleted)); daParent.Update(ds.Tables[PARENTTABLENAME].Select( null, null, DataViewRowState.ModifiedCurrent)); daParent.Update(ds.Tables[PARENTTABLENAME].Select( null, null, DataViewRowState.Added)); daChild.Update(ds.Tables[CHILDTABLENAME].Select( null, null, DataViewRowState.ModifiedCurrent)); daChild.Update(ds.Tables[CHILDTABLENAME].Select( null, null, DataViewRowState.Added)); daParentChild.Update(ds.Tables[PARENTCHILDTABLENAME].Select( null, null, DataViewRowState.Added)); } catch (Exception ex) { MessageBox.Show(ex.Message); } } private void createDataButton_Click(object sender, System.EventArgs e) { // Create parent and child records. CreateData(4,4); UpdateData( ); MessageBox.Show("Data created.","Many-to-Many Relationships", MessageBoxButtons.OK, MessageBoxIcon.Information); } private void modifyButton_Click(object sender, System.EventArgs e) { Random r = new Random((int)DateTime.Now.Ticks); // Randomly delete or modify rows from the child and parent rows. for(int i = ds.Tables[CHILDTABLENAME].Rows.Count; i > 0; i--) { DataRow childRow = ds.Tables[CHILDTABLENAME].Rows[i - 1]; if(r.Next(2) == 0) { childRow[FIELD3_FIELD] = Guid.NewGuid().ToString( ); childRow[FIELD4_FIELD] = Guid.NewGuid().ToString( ); } else childRow.Delete( ); } for(int i = ds.Tables[PARENTTABLENAME].Rows.Count; i > 0; i--) { DataRow parentRow = ds.Tables[PARENTTABLENAME].Rows[i - 1]; if(r.Next(2) == 0) { parentRow[FIELD1_FIELD] = Guid.NewGuid().ToString( ); parentRow[FIELD2_FIELD] = Guid.NewGuid().ToString( ); } else parentRow.Delete( ); } // Randomly delete m-n parent/child relationships. for(int i = ds.Tables[PARENTCHILDTABLENAME].Rows.Count; i > 0; i--) { DataRow parentChildRow = ds.Tables[PARENTCHILDTABLENAME].Rows[i - 1]; if(r.Next(2) == 0) parentChildRow.Delete( ); } // Insert two rows into Parent, Child, and random ParentChild. CreateData(2,2); UpdateData( ); MessageBox.Show("Data randomly modified.", "Many-to-Many Relationships", MessageBoxButtons.OK, MessageBoxIcon.Information); } private void deleteButton_Click(object sender, System.EventArgs e) { // Delete the Parent records. for(int i = ds.Tables[PARENTTABLENAME].Rows.Count; i > 0 ;i--) ds.Tables[PARENTTABLENAME].Rows[i - 1].Delete( ); // Delete the Child records. for(int i = ds.Tables[CHILDTABLENAME].Rows.Count; i > 0; i--) ds.Tables[CHILDTABLENAME].Rows[i - 1].Delete( ); // Delete the ParentChild records. for(int i = ds.Tables[PARENTCHILDTABLENAME].Rows.Count; i > 0 ;i--) ds.Tables[PARENTCHILDTABLENAME].Rows[i - 1].Delete( ); UpdateData( ); MessageBox.Show("Data deleted.", "Many-to-Many Relationships", MessageBoxButtons.OK, MessageBoxIcon.Information); } private void refreshButton_Click(object sender, System.EventArgs e) { ds.Clear( ); LoadData( ); } DiscussionTo avoid referential integrity problems when updating a data source with changes in a DataSet having tables related with a many-to-many relationship, update the rows in the following order:
Pass DataViewRowState.Deleted into the Select( ) method of the DataTable object to get the subset of deleted rows from a table. Similarly, pass DataViewRowState.Added to obtain inserted rows and DataViewRowState.ModifiedCurrent to obtain modified rows. A few more considerations involving the primary key:
|
[ Team LiB ] |