[ Team LiB ] |
Recipe 6.7 Enforcing Business Rules with Column ExpressionsProblemYou need to enforce a business rule based on multiple columns in a table at the user interface tier. SolutionUse expression-based columns to enforce business rules at the user interface tier. The business rule for this solution is that the sum of Field1 and Field2 for a row in the table must be 10. The schema of table TBL0607 used in this solution is shown in Table 6-5.
The sample uses four stored procedures, which are shown in Example 6-21 through Example 6-24:
Example 6-21. Stored procedure: SP0607_DeleteCREATE PROCEDURE SP0607_Delete @Id int AS SET NOCOUNT ON delete from TBL0607 where Id=@Id return 0 Example 6-22. Stored procedure: SP0607_GetCREATE PROCEDURE SP0607_Get @Id int=null AS SET NOCOUNT ON if @Id is not null begin select Id, Field1, Field2 from TBL0607 where Id=@Id return 0 end select Id, Field1, Field2 from TBL0607 return 0 Example 6-23. Stored procedure: SP0607_InsertCREATE PROCEDURE SP0607_Insert @Id int, @Field1 int, @Field2 int AS SET NOCOUNT ON insert TBL0607( Id, Field1, Field2) values ( @Id, @Field1, @Field2) if @@rowcount=0 return 1 return 0 Example 6-24. Stored procedure: SP0607_UpdateCREATE PROCEDURE SP0607_Update @Id int, @Field1 int, @Field2 int AS SET NOCOUNT ON update TBL0607 set Field1=@Field1, Field2=@Field2 where Id=@Id if @@rowcount=0 return 1 return 0 The sample code contains three event handlers:
The C# code is shown in Example 6-25. Example 6-25. File: EnforceBusinessRulesWithColumnExpressionsForm.cs// Namespaces, variables, and constants using System; using System.Configuration; using System.Windows.Forms; using System.Data; using System.Data.SqlClient; private DataTable dt; private SqlDataAdapter da; private const String TABLENAME = "TBL0607"; // Table column name constants private const String ID_FIELD = "Id"; private const String FIELD1_FIELD = "Field1"; private const String FIELD2_FIELD = "Field2"; private const String CONSTRAINT_EXPRESSION = "ConstraintExpression"; // Stored procedure name constants private const String DELETE_SP = "SP0607_Delete"; private const String GET_SP = "SP0607_Get"; private const String INSERT_SP = "SP0607_Insert"; private const String UPDATE_SP = "SP0607_Update"; // Stored procedure parameter name constants for table private const String ID_PARM = "@Id"; private const String FIELD1_PARM = "@Field1"; private const String FIELD2_PARM = "@Field2"; // . . . private void EnforceBusinessRulesWithColumnExpressionsForm_Load( object sender, System.EventArgs e) { DataColumnCollection cols; // Build the table. dt = new DataTable(TABLENAME); cols = dt.Columns; cols.Add(ID_FIELD, typeof(Int32)); cols.Add(FIELD1_FIELD, typeof(Int32)); cols.Add(FIELD2_FIELD, typeof(Int32)); // add the primary key dt.PrimaryKey = new DataColumn[] {cols[ID_FIELD]}; // Expression to evaluate whether the sum of Field1 and Field2 // equals 10 cols.Add(CONSTRAINT_EXPRESSION, typeof(Boolean), FIELD1_FIELD + "+" + FIELD2_FIELD + " = 10"); // Create the DataAdapter, handling the RowUpdating event. da = new SqlDataAdapter( ); da.RowUpdating += new SqlRowUpdatingEventHandler(da_RowUpdating); SqlConnection conn = new SqlConnection( ConfigurationSettings.AppSettings["Sql_ConnectString"]); // Build the select command. SqlCommand selectCommand = new SqlCommand(GET_SP, conn); selectCommand.CommandType = CommandType.StoredProcedure; da.SelectCommand = selectCommand; // Build the delete command. SqlCommand deleteCommand = new SqlCommand(DELETE_SP, conn); deleteCommand.CommandType = CommandType.StoredProcedure; deleteCommand.Parameters.Add(ID_PARM, SqlDbType.Int, 0, ID_FIELD); da.DeleteCommand = deleteCommand; // Build the insert command. SqlCommand insertCommand = new SqlCommand(INSERT_SP, conn); insertCommand.CommandType = CommandType.StoredProcedure; insertCommand.Parameters.Add(ID_PARM, SqlDbType.Int, 0, ID_FIELD); insertCommand.Parameters.Add(FIELD1_PARM, SqlDbType.Int, 0, FIELD1_FIELD); insertCommand.Parameters.Add(FIELD2_PARM, SqlDbType.Int, 0, FIELD2_FIELD); da.InsertCommand = insertCommand; // Build the update command. SqlCommand updateCommand = new SqlCommand(UPDATE_SP, conn); updateCommand.CommandType = CommandType.StoredProcedure; updateCommand.Parameters.Add(ID_PARM, SqlDbType.Int, 0, ID_FIELD); updateCommand.Parameters.Add(FIELD1_PARM, SqlDbType.Int, 0, FIELD1_FIELD); updateCommand.Parameters.Add(FIELD2_PARM, SqlDbType.Int, 0, FIELD2_FIELD); da.UpdateCommand = updateCommand; // Fill the table. da.Fill(dt); // Bind the default view of the table to the grid. dataGrid.DataSource = dt.DefaultView; } private void da_RowUpdating(object sender, SqlRowUpdatingEventArgs e) { // For insert or update statements, check that the // calculated constraint column is true. if((e.StatementType == StatementType.Insert || e.StatementType == StatementType.Update) && !(bool)e.Row[CONSTRAINT_EXPRESSION]) { // Constraint has not been met. // Set an error on the row and skip it. e.Row.RowError = "Constraint error."; e.Status = UpdateStatus.SkipCurrentRow; } } private void updateButton_Click(object sender, System.EventArgs e) { try { da.Update(dt); } catch(Exception ex) { MessageBox.Show(ex.Message); } } DiscussionThe RowUpdating event of the DataAdapter occurs during the Update( ) method before the command to update a row is executed against the data source. The event fires with each row update attempt. The RowUpdating event handler receives an argument of type RowUpdatingEventArgs that provides information specifically related to the event as described in Table 6-6.
Table 6-7 describes the values in the UpdateStatus enumeration used by the Status property of the RowUpdatingEventArgs object.
The Update( ) method of the DataAdapter raises two events for every row in the data source that is updated. The order of the events is:
If zero rows are affected, the DBConcurrencyException is raised during the update operation on a row. This usually indicates a concurrency violation. The solution uses the RowUpdating event of the DataAdapter to check whether the expression column in the DataTable is true, indicating that the business rule has been satisfied, before a database record is updated. If the expression if false, an error is set on the row and the Status is set to SkipCurrentRow preventing the record in the database from being updated and continuing the processing with the next row. |
[ Team LiB ] |