Select your user interface:
Exercise 14: Validate a Simple Batch
You can validate a batch from two areas within the application. To validate within the batch user interface screen, select Edit\Validate menu option. The validation runs on each row in the batch.
You can validate a batch from two areas within the application. To validate within the batch user interface screen, click Validate under Processes. The validation runs on each row in the batch.
Figure: Validate batch menu option
Step 1 - Modify the food items within the batch and validate at commit.
In the example below, I have modified one of the food item names so it is unique and have left the "Coffee" and "Orange Juice" rows the same as to invoke errors when the batch is validated.
-
Within your batch, modify one of the rows so that the name is unique while leaving the other rows the same as to invoke the errors when the batch is validated.
Figure: Validation complete
-
Elect to validate the batch when you commit the batch. This option can be found within the Commit parameters data form after the Commit action is clicked.
Figure: Validate the batch as a commit parameter
When you select the Validate batch before committing checkbox within the Commit parameters data form, the batch is validated before it is committed. If validation errors occur within the batch, then the "Exceptions occurred during batch validation" message appears with the results.
Figure: Validation occurred. No records were processed
Reviewing the behavior of our example and examining the commit logic below, we can deduce that no matter which steps you take to validate the batch, the commit stored procedure logic within the commit data form is executed with a transaction that is rolled back.
Step 2 - Review the Commit Add data form.
Looking at the Commit Add data form below, review the commit logic which is called by the Infinity platform for each row in the batch.
<AddDataFormTemplateSpec xmlns:common="bb_appfx_commontypes" ID="96d20428-a73d-44a7-ac74-44a75bfada37" Name="Food Item Add Batch Row Commit Add Form" Description="Adds a food item record to the system from a given batch." Author="Technical Training" RecordType="Food Item" DataFormInstanceID="4b79233b-cbb3-4ab8-a78f-01f25a11b322" common:SecurityUIFolder="Constituent\Food Bank\Food Item\Batch" SecurityUIDisplayFeature="false" xmlns="bb_appfx_adddataformtemplate" > <common:DependencyList> <common:Dependency CatalogAssembly="Blackbaud.CustomFx.BatchTraining.Catalog.dll" CatalogItem="Blackbaud.CustomFx.BatchTraining.Catalog.CopyOfFoodItemBatch.Add.xml" /> </common:DependencyList> <SPDataForm> <SaveImplementation SPName="USR_USP_DATAFORMTEMPLATE_ADD_BATCHFOODITEMADDBATCHCOMMIT"> <common:CreateProcedureSQL><![CDATA[ create procedure dbo.USR_USP_DATAFORMTEMPLATE_ADD_BATCHFOODITEMADDBATCHCOMMIT ( @ID uniqueidentifier = null output, @VALIDATEONLY bit = 0, @CHANGEAGENTID uniqueidentifier, @NAME nvarchar(100) = '', @DESCRIPTION nvarchar(max) = '', @CURRENTCOST money = 0.00, @LOWINVENTORYTHRESHOLD smallint = 0, @WEIGHT decimal(10,2) = 0.00 ) as set nocount on; declare @CURRENTDATE datetime; if @ID is null set @ID = newid(); if @CHANGEAGENTID is null exec dbo.USP_CHANGEAGENT_GETORCREATECHANGEAGENT @CHANGEAGENTID output; set @CURRENTDATE = getdate(); begin try exec dbo.USR_USP_DATAFORMTEMPLATE_ADD_FOOD_ITEMBATCH @ID = @ID output, @CHANGEAGENTID = @CHANGEAGENTID, @NAME = @NAME, @DESCRIPTION = @DESCRIPTION, @CURRENTCOST = @CURRENTCOST, @LOWINVENTORYTHRESHOLD = @LOWINVENTORYTHRESHOLD, @WEIGHT = @WEIGHT; end try begin catch exec.dbo.USP_RAISE_ERROR; return 1; end catch return 0; ]]> </common:CreateProcedureSQL> <common:ExpectedDBExceptions> <common:Constraints> <common:Constraint Name="UC_USR_FOODITEM_NAME" Field="NAME" Type="Unique" /> <common:Constraint Name="CK_USR_FOODITEM_NAME" Field="NAME" Type="Required" /> </common:Constraints> </common:ExpectedDBExceptions> </SaveImplementation> </SPDataForm> <common:FormMetaData> <common:FormFields> <common:FormField FieldID="VALIDATEONLY" DataType="Boolean" Caption="Validate only" /> <common:FormField FieldID="NAME" Required="true" MaxLength="100" Caption="Name" /> <common:FormField FieldID="DESCRIPTION" Caption="Description" DefaultValueText="" /> <common:FormField FieldID="CURRENTCOST" Required="true" DataType="Money" Caption="Current Cost" DefaultValueText="0.00" /> <common:FormField FieldID="LOWINVENTORYTHRESHOLD" DataType="SmallInt" Caption="Low Inventory Threshold" DefaultValueText="0" /> <common:FormField FieldID="WEIGHT" DataType="Decimal" Caption="Weight" Description="The weight of a food item." Precision="10" Scale="2" DefaultValueText="0.00" /> </common:FormFields> </common:FormMetaData> </AddDataFormTemplateSpec>
At the heart of the commit spec is the USR_USP_DATAFORMTEMPLATE_ADD_BATCHFOODITEMADDBATCHCOMMIT stored procedure that wraps a call to USR_USP_DATAFORMTEMPLATE_ADD_FOOD_ITEMBATCH which attempts to insert a row into the USR_FOODITEM table.
exec dbo.USR_USP_DATAFORMTEMPLATE_ADD_FOOD_ITEMBATCH @ID = @ID output, @CHANGEAGENTID = @CHANGEAGENTID, @NAME = @NAME, @DESCRIPTION = @DESCRIPTION, @CURRENTCOST = @CURRENTCOST, @LOWINVENTORYTHRESHOLD = @LOWINVENTORYTHRESHOLD, @WEIGHT = @WEIGHT;
@ValidateOnly Flag
Look closely at the commit spec, do you notice anything interesting with the form fields and the parameters for the wrapping USR_USP_DATAFORMTEMPLATE_ADD_BATCHFOODITEMADDBATCHCOMMIT stored procedure? Did you notice the FormField element with a FieldID attribute equal to "VALIDATEONLY" and the corresponding parameter? What is this purpose of VALIDATEONLY? The Infinity platform passes a value of 1 to the @VALIDATEONLY parameter whenever the user validates the batch. The purpose of the @VALIDATEONLY flag is to provide a way for the database developer to determine if the user opts to validate the batch. If the value is 1, then you can write code within your save implementation that respects the @VALIDATEONLY flag. How you respect (or not respect) the @VALIDATEONLY flag is up to you. For example, if the flag is true then you could only perform checks that inspect incoming data against the production data via Transact-SQL SELECT statements and completely bypass the commit logic. Or, you can provide additional logic within your stored procedure in addition to the commit logic which would include the native database constraint checks that naturally occur when a row is saved to the table.
Keep in mind that if you run validation and choose not to bypass the commit logic, then the commit data logic is executed within a transaction that is rolled back. Until the transaction is rolled back, the logic has a hold of database resources referenced by the logic's Transact-SQL, such as database tables. Whenever you deal with a large number of rows in the batch or your commit logic is complicated and involves many tables, then you may run across resource contention issues. Resource contention issues arise when one or more queries consume excessive resources on the SQL Server, resulting in several different (sometimes unrelated) queries apparently blocking each other. The net effect can be a system that seems slow, certain operations time out, etc. You get the picture. The problem of resource contention is magnified the more rows you attempt to validate and commit within your batches and the more complicated your commit and validation logic.
Step 3 - Add @VALIDATEONLY logic.
-
Review the logic within the commit stored procedure; we do not currently utilize this flag.
-
Modify our commit logic and check for the value of the @VALIDATEONLY flag and react when it's equal to 1. We use the Transact-SQL RAISERROR statement to generate a user-defined error message that is returned as a server error message to the calling application or in our case the associated CATCH block within the stored procedure.
-
Let's pretend we have a requirement that states "Ensure the food item description is descriptive. Do not allow blank descriptions or descriptions that are merely a copy of the food item name." Now granted, you could pull this requirement off with a check constraint on the table; but this example demonstrates the behavior of custom stored procedure error checking logic in the batch and provides visibility to when @VALIDATEONLY is set to 1 by the Infinity platform.
-
In addition, we check for the existence of an existing food item by querying for the food item name when @VALIDATEONLY is true. On the other hand, when @VALIDATEONLY is false, then we attempt to commit the row without performing the additional validation checks.
Figure: Add the validation logic
Step 4 - Test the validation logic.
-
Modify one of the batch rows. Make the description equal to the name.
Figure: Test the validation logic
This causes our custom error message to be raised from the stored procedure and reported within the batch. The Coffee and Orange Juice rows were caught in the validation by the SELECT statement which scans for a duplicate name.
-
Manually validate the batch by selecting Edit\Validate batch from the main menu.
Figure: Manually validate the rows and the resulting messages
Notice that both the custom error messages were raised from the validation portion of our commit stored procedure. We did not attempt to commit the data to the production table and therefore did not trigger the violation of the unique constraint on the production table. Also note that since we manually validated the batch, that @VALIDATEONLY is equal to 1.
-
Try to commit the batch without validating the batch before committing.
Figure: Commit without validation
Figure: Database constraints during commit
Opening the committed batch reveals the expected database exception for the duplicate food item name; but since we did not validate the batch before committing the new custom validation code was not executed because the @VALIDATEONLY flag was set to zero.
Figure: Database constraints during commit