Professional Documents
Culture Documents
1 of28
DoNothingifconcurrentusersaremodifyingthesamerecord,letthelastcommitwin(thedefault behavior) OptimisticConcurrency assumethatwhiletheremaybeconcurrencyconflictseverynowandthen,the vastmajorityofthetimesuchconflictswon'tarisetherefore,ifaconflictdoesarise,simplyinformtheuser thattheirchangescan'tbesavedbecauseanotheruserhasmodifiedthesamedata PessimisticConcurrency assumethatconcurrencyconflictsarecommonplaceandthatuserswon'ttolerate beingtoldtheirchangesweren'tsavedduetoanotheruser'sconcurrentactivitytherefore,whenoneuser startsupdatingarecord,lockit,therebypreventinganyotherusersfromeditingordeletingthatrecorduntil theusercommitstheirmodifications
Allofourtutorialsthusfarhaveusedthedefaultconcurrencyresolutionstrategy namely,we'veletthelastwrite win.Inthistutorialwe'llexaminehowtoimplementoptimisticconcurrencycontrol. Note: Wewon'tlookatpessimisticconcurrencyexamplesinthistutorialseries.Pessimisticconcurrencyisrarely usedbecausesuchlocks,ifnotproperlyrelinquished,canpreventotherusersfromupdatingdata.Forexample,ifa userlocksarecordforeditingandthenleavesforthedaybeforeunlockingit,nootheruserwillbeabletoupdate thatrecorduntiltheoriginaluserreturnsandcompleteshisupdate.Therefore,insituationswherepessimistic concurrencyisused,there'stypicallyatimeout that,ifreached,cancelsthelock.Ticketsaleswebsites,whichlock aparticularseatinglocationforshortperiodwhiletheusercompletestheorderprocess,isanexampleof pessimisticconcurrencycontrol.
Step1:LookingatHowOptimisticConcurrencyis Implemented
Optimisticconcurrencycontrolworksbyensuringthattherecordbeingupdatedordeletedhasthesamevaluesas itdidwhentheupdatingordeletingprocessstarted.Forexample,whenclickingtheEditbuttoninaneditable GridView,therecord'svaluesarereadfromthedatabaseanddisplayedinTextBoxesandotherWebcontrols. TheseoriginalvaluesaresavedbytheGridView.Later,aftertheusermakesherchangesandclickstheUpdate button,theoriginalvaluesplusthenewvaluesaresenttotheBusinessLogicLayer,andthendowntotheData AccessLayer.TheDataAccessLayermustissueaSQLstatementthatwillonlyupdatetherecordiftheoriginal valuesthattheuserstartededitingareidenticaltothevaluesstillinthedatabase.Figure2depictsthissequenceof events.
2 of28
Figure2:FortheUpdateorDeletetoSucceed,theOriginalValuesMustBeEqualtotheCurrentDatabase Values Therearevariousapproachestoimplementingoptimisticconcurrency(see PeterA.Bromberg'sOptmistic ConcurrencyUpdatingLogicforabrieflookatanumberofoptions).TheADO.NETTypedDataSetprovidesone implementationthatcanbeconfiguredwithjustthetickofacheckbox.Enablingoptimisticconcurrencyfora TableAdapterintheTypedDataSetaugmentstheTableAdapter'sUPDATE andDELETE statementstoincludea comparisonofalloftheoriginalvaluesintheWHERE clause.ThefollowingUPDATE statement,forexample,updates thenameandpriceofaproductonlyifthecurrentdatabasevaluesareequaltothevaluesthatwereoriginally retrievedwhenupdatingtherecordintheGridView.The@ProductName and@UnitPrice parameterscontainthe newvaluesenteredbytheuser,whereas@original_ProductName and@original_UnitPrice containthevalues thatwereoriginallyloadedintotheGridViewwhentheEditbuttonwasclicked:
UPDATEProductsSET ProductName=@ProductName, UnitPrice=@UnitPrice WHERE ProductID=@original_ProductIDAND
3 of28
ProductName=@original_ProductNameAND UnitPrice=@original_UnitPrice
Note: ThisUPDATE statementhasbeensimplifiedforreadability.Inpractice,theUnitPrice checkintheWHERE clausewouldbemoreinvolvedsinceUnitPrice cancontainNULLsandcheckingifNULL=NULL alwaysreturns False(insteadyoumustuseISNULL). InadditiontousingadifferentunderlyingUPDATE statement,configuringaTableAdaptertouseoptimistic concurrencyalsomodifiesthesignatureofitsDBdirectmethods.Recallfromourfirsttutorial, CreatingaData AccessLayer,thatDBdirectmethodswerethosethatacceptsalistofscalarvaluesasinputparameters(ratherthan astronglytypedDataRoworDataTableinstance).Whenusingoptimisticconcurrency,theDBdirectUpdate() andDelete() methodsincludeinputparametersfortheoriginalvaluesaswell.Moreover,thecodeintheBLLfor usingthebatchupdatepattern(theUpdate() methodoverloadsthatacceptDataRowsandDataTablesratherthan scalarvalues)mustbechangesaswell. RatherthanextendourexistingDAL'sTableAdapterstouseoptimisticconcurrency(whichwouldnecessitate changingtheBLLtoaccommodate),let'sinsteadcreateanewTypedDataSetnamed NorthwindOptimisticConcurrency,towhichwe'lladdaProducts TableAdapterthatusesoptimistic concurrency.Followingthat,we'llcreateaProductsOptimisticConcurrencyBLL BusinessLogicLayerclassthat hastheappropriatemodificationstosupporttheoptimisticconcurrencyDAL.Oncethisgroundworkhasbeenlaid, we'llbereadytocreatetheASP.NETpage.
Step2:CreatingaDataAccessLayerThatSupports OptimisticConcurrency
TocreateanewTypedDataSet,rightclickontheDAL folderwithintheApp_Code folderandaddanewDataSet namedNorthwindOptimisticConcurrency.Aswesawinthefirsttutorial,doingsowilladdanewTableAdapter totheTypedDataSet,automaticallylaunchingtheTableAdapterConfigurationWizard.Inthefirstscreen,we're promptedtospecifythedatabasetoconnectto connecttothesameNorthwinddatabaseusingthe NORTHWNDConnectionString settingfromWeb.config.
4 of28
5 of28
6 of28
Figure8:ADataTableandTableAdapterHaveBeenAddedtotheTypedDataSet ToseethedifferencesbetweentheUPDATE andDELETE queriesbetweentheProductsOptimisticConcurrency TableAdapter(whichusesoptimisticconcurrency)andtheProductsTableAdapter(whichdoesn't),clickonthe TableAdapterandgotothePropertieswindow.IntheDeleteCommand andUpdateCommand properties' CommandText subpropertiesyoucanseetheactualSQLsyntaxthatissenttothedatabasewhentheDAL'supdate ordeleterelatedmethodsareinvoked.FortheProductsOptimisticConcurrency TableAdapterthe DELETE 7 of28
statementusedis:
DELETEFROM[Products] WHERE(([ProductID]=@Original_ProductID) AND([ProductName]=@Original_ProductName) AND((@IsNull_SupplierID=1AND[SupplierID]ISNULL) OR([SupplierID]=@Original_SupplierID)) AND((@IsNull_CategoryID=1AND[CategoryID]ISNULL) OR([CategoryID]=@Original_CategoryID)) AND((@IsNull_QuantityPerUnit=1AND[QuantityPerUnit]ISNULL) OR([QuantityPerUnit]=@Original_QuantityPerUnit)) AND((@IsNull_UnitPrice=1AND[UnitPrice]ISNULL) OR([UnitPrice]=@Original_UnitPrice)) AND((@IsNull_UnitsInStock=1AND[UnitsInStock]ISNULL) OR([UnitsInStock]=@Original_UnitsInStock)) AND((@IsNull_UnitsOnOrder=1AND[UnitsOnOrder]ISNULL) OR([UnitsOnOrder]=@Original_UnitsOnOrder)) AND((@IsNull_ReorderLevel=1AND[ReorderLevel]ISNULL) OR([ReorderLevel]=@Original_ReorderLevel)) AND([Discontinued]=@Original_Discontinued))
WhereastheDELETE statementfortheProductTableAdapterinouroriginalDAListhemuchsimpler:
DELETEFROM[Products]WHERE(([ProductID]=@Original_ProductID))
Asyoucansee,theWHERE clauseintheDELETE statementfortheTableAdapterthatusesoptimisticconcurrency includesacomparisonbetweeneachoftheProduct table'sexistingcolumnvaluesandtheoriginalvaluesatthe timetheGridView(orDetailsVieworFormView)waslastpopulated.SinceallfieldsotherthanProductID, ProductName,andDiscontinued canhaveNULL values,additionalparametersandchecksareincludedto correctlycompareNULL valuesintheWHERE clause. Wewon'tbeaddinganyadditionalDataTablestotheoptimisticconcurrencyenabledDataSetforthistutorial,as ourASP.NETpagewillonlyprovideupdatinganddeletingproductinformation.However,wedostillneedtoadd theGetProductByProductID(productID) methodtotheProductsOptimisticConcurrency TableAdapter. Toaccomplishthis,rightclickontheTableAdapter'stitlebar(thearearightabovetheFill andGetProducts methodnames)andchooseAddQueryfromthecontextmenu.ThiswilllaunchtheTableAdapterQuery ConfigurationWizard.AswithourTableAdapter'sinitialconfiguration,opttocreatetheGetProductByProductID (productID) methodusinganadhocSQLstatement(seeFigure4).SincetheGetProductByProductID (productID) methodreturnsinformationaboutaparticularproduct,indicatethatthisqueryisaSELECT querytype thatreturnsrows.
8 of28
9 of28
Step3:CreatingaBusinessLogicLayerforthe OptimisticConcurrencyEnabledDAL
OurexistingProductsBLL classhasexamplesofusingboththebatchupdateandDBdirectpatterns.The AddProduct methodandUpdateProduct overloadsbothusethebatchupdatepattern,passinginaProductRow instancetotheTableAdapter'sUpdatemethod.TheDeleteProduct method,ontheotherhand,usestheDBdirect pattern,callingtheTableAdapter's Delete(productID) method. WiththenewProductsOptimisticConcurrency TableAdapter,theDBdirectmethodsnowrequirethatthe originalvaluesalsobepassedin.Forexample,theDelete methodnowexpectsteninputparameters:theoriginal ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock , UnitsOnOrder,ReorderLevel,andDiscontinued.Itusestheseadditionalinputparameters'valuesinWHERE clauseoftheDELETE statementsenttothedatabase,onlydeletingthespecifiedrecordifthedatabase'scurrent valuesmapuptotheoriginalones. WhilethemethodsignaturefortheTableAdapter'sUpdate methodusedinthebatchupdatepatternhasn'tchanged, thecodeneededtorecordtheoriginalandnewvalueshas.Therefore,ratherthanattempttousetheoptimistic concurrencyenabledDALwithourexistingProductsBLL class,let'screateanewBusinessLogicLayerclassfor workingwithournewDAL. AddaclassnamedProductsOptimisticConcurrencyBLL totheBLL folderwithintheApp_Code folder.
10 of28
11 of28
NotetheusingNorthwindOptimisticConcurrencyTableAdapters statementabovethestartoftheclass declaration.TheNorthwindOptimisticConcurrencyTableAdapters namespacecontainsthe ProductsOptimisticConcurrencyTableAdapter class,whichprovidestheDAL'smethods.Alsobeforetheclass declarationyou'llfindtheSystem.ComponentModel.DataObject attribute,whichinstructsVisualStudioto includethisclassintheObjectDataSourcewizard'sdropdownlist. TheProductsOptimisticConcurrencyBLL'sAdapter propertyprovidesquickaccesstoaninstanceofthe ProductsOptimisticConcurrencyTableAdapter class,andfollowsthepatternusedinouroriginalBLLclasses (ProductsBLL,CategoriesBLL,andsoon).Finally,theGetProducts() methodsimplycallsdownintotheDAL's GetProdcuts() methodandreturnsaProductsOptimisticConcurrencyDataTable objectpopulatedwitha ProductsOptimisticConcurrencyRow instanceforeachproductrecordinthedatabase.
DeletingaProductUsingtheDBDirectPatternwith OptimisticConcurrency
WhenusingtheDBdirectpatternagainstaDALthatusesoptimisticconcurrency,themethodsmustbepassedthe newandoriginalvalues.Fordeleting,therearenonewvalues,soonlytheoriginalvaluesneedbepassedin.Inour BLL,then,wemustacceptalloftheoriginalparametersasinputparameters.Let'shavetheDeleteProduct methodintheProductsOptimisticConcurrencyBLL classusetheDBdirectmethod.Thismeansthatthismethod needstotakeinalltenproductdatafieldsasinputparameters,andpassthesetotheDAL,asshowninthe followingcode:
[System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Delete,true)] publicboolDeleteProduct(intoriginal_productID,stringoriginal_productName, int?original_supplierID,int?original_categoryID,stringoriginal_quantityPerUnit, decimal?original_unitPrice,short?original_unitsInStock, short?original_unitsOnOrder,short?original_reorderLevel,booloriginal_discontinued) { introwsAffected=Adapter.Delete(original_productID, original_productName, original_supplierID, original_categoryID, original_quantityPerUnit, original_unitPrice, original_unitsInStock, original_unitsOnOrder, original_reorderLevel, original_discontinued) //Returntrueifpreciselyonerowwasdeleted,otherwisefalse returnrowsAffected==1 }
Iftheoriginalvalues thosevaluesthatwerelastloadedintotheGridView(orDetailsVieworFormView) differ fromthevaluesinthedatabasewhentheuserclickstheDeletebuttontheWHERE clausewon'tmatchupwithany databaserecordandnorecordswillbeaffected.Hence,theTableAdapter'sDelete methodwillreturn0 andthe BLL'sDeleteProduct methodwillreturnfalse.
12 of28
UpdatingaProductUsingtheBatchUpdatePattern withOptimisticConcurrency
Asnotedearlier,theTableAdapter'sUpdate methodforthebatchupdatepatternhasthesamemethodsignature regardlessofwhetherornotoptimisticconcurrencyisemployed.Namely,theUpdate methodexpectsaDataRow, anarrayofDataRows,aDataTable,oraTypedDataSet.Therearenoadditionalinputparametersforspecifying theoriginalvalues.ThisispossiblebecausetheDataTablekeepstrackoftheoriginalandmodifiedvaluesforits DataRow(s).WhentheDALissuesitsUPDATE statement,the@original_ColumnName parametersarepopulated withtheDataRow'soriginalvalues,whereasthe@ColumnName parametersarepopulatedwiththeDataRow's modifiedvalues. IntheProductsBLL class(whichusesouroriginal,nonoptimisticconcurrencyDAL),whenusingthebatchupdate patterntoupdateproductinformationourcodeperformsthefollowingsequenceofevents: 1. ReadthecurrentdatabaseproductinformationintoaProductRow instanceusingtheTableAdapter's GetProductByProductID(productID) method 2. AssignthenewvaluestotheProductRow instancefromStep1 3. CalltheTableAdapter'sUpdate method,passingintheProductRow instance Thissequenceofsteps,however,won'tcorrectlysupportoptimisticconcurrencybecausetheProductRow populatedinStep1ispopulateddirectlyfromthedatabase,meaningthattheoriginalvaluesusedbytheDataRow arethosethatcurrentlyexistinthedatabase,andnotthosethatwereboundtotheGridViewatthestartofthe editingprocess.Instead,whenusinganoptimisticconcurrencyenabledDAL,weneedtoaltertheUpdateProduct methodoverloadstousethefollowingsteps: 1. ReadthecurrentdatabaseproductinformationintoaProductsOptimisticConcurrencyRow instanceusing theTableAdapter'sGetProductByProductID(productID) method 2. Assigntheoriginal valuestotheProductsOptimisticConcurrencyRow instancefromStep1 3. CalltheProductsOptimisticConcurrencyRow instance's AcceptChanges() method,whichinstructsthe DataRowthatitscurrentvaluesarethe"original"ones 4. Assignthenew valuestotheProductsOptimisticConcurrencyRow instance 5. CalltheTableAdapter'sUpdate method,passingintheProductsOptimisticConcurrencyRow instance Step1readsinallofthecurrentdatabasevaluesforthespecifiedproductrecord.Thisstepissuperfluousinthe UpdateProduct overloadthatupdatesall oftheproductcolumns(asthesevaluesareoverwritteninStep2),butis essentialforthoseoverloadswhereonlyasubsetofthecolumnvaluesarepassedinasinputparameters.Oncethe originalvalueshavebeenassignedtotheProductsOptimisticConcurrencyRow instance,theAcceptChanges() methodiscalled,whichmarksthecurrentDataRowvaluesastheoriginalvaluestobeusedinthe @original_ColumnName parametersintheUPDATE statement.Next,thenewparametervaluesareassignedtothe ProductsOptimisticConcurrencyRow and,finally,theUpdate methodisinvoked,passingintheDataRow. ThefollowingcodeshowstheUpdateProduct overloadthatacceptsallproductdatafieldsasinputparameters. Whilenotshownhere,theProductsOptimisticConcurrencyBLL classincludedinthedownloadforthistutorial alsocontainsanUpdateProduct overloadthatacceptsjusttheproduct'snameandpriceasinputparameters.
protectedvoidAssignAllProductValues( NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRowproduct, stringproductName,int?supplierID,int?categoryID,stringquantityPerUnit, decimal?unitPrice,short?unitsInStock,short?unitsOnOrder,short?reorderLevel, booldiscontinued) { product.ProductName=productName
13 of28
if(supplierID==null) product.SetSupplierIDNull() else product.SupplierID=supplierID.Value if(categoryID==null) product.SetCategoryIDNull() else product.CategoryID=categoryID.Value if(quantityPerUnit==null) product.SetQuantityPerUnitNull() else product.QuantityPerUnit=quantityPerUnit if(unitPrice==null) product.SetUnitPriceNull() else product.UnitPrice=unitPrice.Value if(unitsInStock==null) product.SetUnitsInStockNull() else product.UnitsInStock=unitsInStock.Value if(unitsOnOrder==null) product.SetUnitsOnOrderNull() else product.UnitsOnOrder=unitsOnOrder.Value if(reorderLevel==null) product.SetReorderLevelNull() else product.ReorderLevel=reorderLevel.Value product.Discontinued=discontinued } [System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Update,true)] publicboolUpdateProduct( //newparametervalues stringproductName,int?supplierID,int?categoryID,stringquantityPerUnit, decimal?unitPrice,short?unitsInStock,short?unitsOnOrder,short?reorderLevel, booldiscontinued,intproductID, //originalparametervalues stringoriginal_productName,int?original_supplierID,int?original_categoryID, stringoriginal_quantityPerUnit,decimal?original_unitPrice, short?original_unitsInStock,short?original_unitsOnOrder, short?original_reorderLevel,booloriginal_discontinued, intoriginal_productID) { //STEP1:Readinthecurrentdatabaseproductinformation NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTableproducts= Adapter.GetProductByProductID(original_productID) if(products.Count==0) //nomatchingrecordfound,returnfalse returnfalse NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRowproduct=products[0] //STEP2:Assigntheoriginalvaluestotheproductinstance AssignAllProductValues(product,original_productName,original_supplierID, original_categoryID,original_quantityPerUnit,original_unitPrice, original_unitsInStock,original_unitsOnOrder,original_reorderLevel, original_discontinued) //STEP3:Acceptthechanges product.AcceptChanges() //STEP4:Assignthenewvaluestotheproductinstance AssignAllProductValues(product,productName,supplierID,categoryID,
14 of28
Step4:PassingtheOriginalandNewValuesFrom theASP.NETPagetotheBLLMethods
WiththeDALandBLLcomplete,allthatremainsistocreateanASP.NETpagethatcanutilizetheoptimistic concurrencylogicbuiltintothesystem.Specifically,thedataWebcontrol(theGridView,DetailsView,or FormView)mustrememberitsoriginalvaluesandtheObjectDataSourcemustpassbothsetsofvaluestothe BusinessLogicLayer.Furthermore,theASP.NETpagemustbeconfiguredtogracefullyhandleconcurrency violations. StartbyopeningtheOptimisticConcurrency.aspx pageintheEditInsertDelete folderandaddinga GridViewtotheDesigner,settingitsID propertytoProductsGrid.FromtheGridView'ssmarttag,opttocreatea newObjectDataSourcenamedProductsOptimisticConcurrencyDataSource.Sincewewantthis ObjectDataSourcetousetheDALthatsupportsoptimisticconcurrency,configureittousethe ProductsOptimisticConcurrencyBLL object.
ConfiguringtheObjectDataSourceControl's
15 of28
Properties
Aftercompletingthewizard,theObjectDataSource'sdeclarativemarkupshouldlooklikethefollowing:
<asp:ObjectDataSourceID="ProductsOptimisticConcurrencyDataSource"runat="server" DeleteMethod="DeleteProduct"OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts"TypeName="ProductsOptimisticConcurrencyBLL" UpdateMethod="UpdateProduct"> <DeleteParameters> <asp:ParameterName="original_productID"Type="Int32"/> <asp:ParameterName="original_productName"Type="String"/> <asp:ParameterName="original_supplierID"Type="Int32"/> <asp:ParameterName="original_categoryID"Type="Int32"/> <asp:ParameterName="original_quantityPerUnit"Type="String"/> <asp:ParameterName="original_unitPrice"Type="Decimal"/> <asp:ParameterName="original_unitsInStock"Type="Int16"/> <asp:ParameterName="original_unitsOnOrder"Type="Int16"/> <asp:ParameterName="original_reorderLevel"Type="Int16"/> <asp:ParameterName="original_discontinued"Type="Boolean"/> </DeleteParameters> <UpdateParameters> <asp:ParameterName="productName"Type="String"/> <asp:ParameterName="supplierID"Type="Int32"/> <asp:ParameterName="categoryID"Type="Int32"/> <asp:ParameterName="quantityPerUnit"Type="String"/> <asp:ParameterName="unitPrice"Type="Decimal"/> <asp:ParameterName="unitsInStock"Type="Int16"/> <asp:ParameterName="unitsOnOrder"Type="Int16"/> <asp:ParameterName="reorderLevel"Type="Int16"/> <asp:ParameterName="discontinued"Type="Boolean"/> <asp:ParameterName="productID"Type="Int32"/> <asp:ParameterName="original_productName"Type="String"/> <asp:ParameterName="original_supplierID"Type="Int32"/> <asp:ParameterName="original_categoryID"Type="Int32"/> <asp:ParameterName="original_quantityPerUnit"Type="String"/> <asp:ParameterName="original_unitPrice"Type="Decimal"/> <asp:ParameterName="original_unitsInStock"Type="Int16"/> <asp:ParameterName="original_unitsOnOrder"Type="Int16"/> <asp:ParameterName="original_reorderLevel"Type="Int16"/> <asp:ParameterName="original_discontinued"Type="Boolean"/> <asp:ParameterName="original_productID"Type="Int32"/> </UpdateParameters> </asp:ObjectDataSource>
Asyoucansee,theDeleteParameters collectioncontainsaParameter instanceforeachoftheteninput parametersintheProductsOptimisticConcurrencyBLL class's DeleteProduct method.Likewise,the UpdateParameters collectioncontainsaParameter instanceforeachoftheinputparametersinUpdateProduct. Forthoseprevioustutorialsthatinvolveddatamodification,we'dremovetheObjectDataSource's OldValuesParameterFormatString propertyatthispoint,sincethispropertyindicatesthattheBLLmethod expectstheold(ororiginal)valuestobepassedinaswellasthenewvalues.Furthermore,thispropertyvalue indicatestheinputparameternamesfortheoriginalvalues.Sincewearepassingintheoriginalvaluesintothe BLL,donot removethisproperty. Note: ThevalueoftheOldValuesParameterFormatString propertymustmaptotheinputparameternamesin theBLLthatexpecttheoriginalvalues.Sincewenamedtheseparametersoriginal_productName, original_supplierID,andsoon,youcanleavetheOldValuesParameterFormatString propertyvalueas original_{0}.If,however,theBLLmethods'inputparametershadnameslikeold_productName , old_supplierID,andsoon,you'dneedtoupdatetheOldValuesParameterFormatString propertytoold_{0}.
16 of28
OverwriteChanges
thedefaultvaluedoesnotsendtheoriginalvaluestotheBLLmethods'originalinput
parameters
l
CompareAllValues
doessendtheoriginalvaluestotheBLLmethodschoosethisoptionwhenusing optimisticconcurrency
TakeamomenttosettheConflictDetection propertytoCompareAllValues.
ConfiguringtheGridView'sPropertiesandFields
WiththeObjectDataSource'spropertiesproperlyconfigured,let'sturnourattentiontosettinguptheGridView. First,sincewewanttheGridViewtosupporteditinganddeleting,clicktheEnableEditingandEnableDeleting checkboxesfromtheGridView'ssmarttag.ThiswilladdaCommandFieldwhoseShowEditButton and ShowDeleteButton arebothsettotrue. WhenboundtotheProductsOptimisticConcurrencyDataSource ObjectDataSource,theGridViewcontainsa fieldforeachoftheproduct'sdatafields.WhilesuchaGridViewcanbeedited,theuserexperienceisanythingbut acceptable.TheCategoryID andSupplierID BoundFieldswillrenderasTextBoxes,requiringtheusertoenter theappropriatecategoryandsupplierasIDnumbers.Therewillbenoformattingforthenumericfieldsandno validationcontrolstoensurethattheproduct'snamehasbeensuppliedandthattheunitprice,unitsinstock,units onorder,andreorderlevelvaluesarebothpropernumericvaluesandaregreaterthanorequaltozero. AswediscussedintheAddingValidationControlstotheEditingandInsertingInterfaces andCustomizingthe DataModificationInterfacetutorials,theuserinterfacecanbecustomizedbyreplacingtheBoundFieldswith TemplateFields.I'vemodifiedthisGridViewanditseditinginterfaceinthefollowingways:
l l l
RemovedtheProductID,SupplierName,andCategoryName BoundFields ConvertedtheProductName BoundFieldtoaTemplateFieldandaddedaRequiredFieldValidationcontrol. ConvertedtheCategoryID andSupplierID BoundFieldstoTemplateFields,andadjustedtheediting interfacetouseDropDownListsratherthanTextBoxes.IntheseTemplateFields'ItemTemplates,the CategoryName andSupplierName datafieldsaredisplayed. ConvertedtheUnitPrice,UnitsInStock,UnitsOnOrder,andReorderLevel BoundFieldsto TemplateFieldsandaddedCompareValidatorcontrols.
Sincewe'vealreadyexaminedhowtoaccomplishthesetasksinprevioustutorials,I'lljustlistthefinaldeclarative syntaxhereandleavetheimplementationaspractice.
<asp:GridViewID="ProductsGrid"runat="server"AutoGenerateColumns="False" DataKeyNames="ProductID"DataSourceID="ProductsOptimisticConcurrencyDataSource" OnRowUpdated="ProductsGrid_RowUpdated"> <Columns> <asp:CommandFieldShowDeleteButton="True"ShowEditButton="True"/> <asp:TemplateFieldHeaderText="Product"SortExpression="ProductName"> <EditItemTemplate> <asp:TextBoxID="EditProductName"runat="server" Text='<%#Bind("ProductName")%>'></asp:TextBox> <asp:RequiredFieldValidatorID="RequiredFieldValidator1" ControlToValidate="EditProductName" ErrorMessage="Youmustenteraproductname." runat="server">*</asp:RequiredFieldValidator> </EditItemTemplate> <ItemTemplate>
17 of28
<asp:LabelID="Label1"runat="server" Text='<%#Bind("ProductName")%>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:TemplateFieldHeaderText="Category"SortExpression="CategoryName"> <EditItemTemplate> <asp:DropDownListID="EditCategoryID"runat="server" DataSourceID="CategoriesDataSource"AppendDataBoundItems="true" DataTextField="CategoryName"DataValueField="CategoryID" SelectedValue='<%#Bind("CategoryID")%>'> <asp:ListItemValue="">(None)</asp:ListItem> </asp:DropDownList><asp:ObjectDataSourceID="CategoriesDataSource" runat="server"OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories"TypeName="CategoriesBLL"> </asp:ObjectDataSource> </EditItemTemplate> <ItemTemplate> <asp:LabelID="Label2"runat="server" Text='<%#Bind("CategoryName")%>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:TemplateFieldHeaderText="Supplier"SortExpression="SupplierName"> <EditItemTemplate> <asp:DropDownListID="EditSuppliersID"runat="server" DataSourceID="SuppliersDataSource"AppendDataBoundItems="true" DataTextField="CompanyName"DataValueField="SupplierID" SelectedValue='<%#Bind("SupplierID")%>'> <asp:ListItemValue="">(None)</asp:ListItem> </asp:DropDownList><asp:ObjectDataSourceID="SuppliersDataSource" runat="server"OldValuesParameterFormatString="original_{0}" SelectMethod="GetSuppliers"TypeName="SuppliersBLL"> </asp:ObjectDataSource> </EditItemTemplate> <ItemTemplate> <asp:LabelID="Label3"runat="server" Text='<%#Bind("SupplierName")%>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:BoundFieldDataField="QuantityPerUnit"HeaderText="Qty/Unit" SortExpression="QuantityPerUnit"/> <asp:TemplateFieldHeaderText="Price"SortExpression="UnitPrice"> <EditItemTemplate> <asp:TextBoxID="EditUnitPrice"runat="server" Text='<%#Bind("UnitPrice","{0:N2}")%>'Columns="8"/> <asp:CompareValidatorID="CompareValidator1"runat="server" ControlToValidate="EditUnitPrice" ErrorMessage="Unitpricemustbeavalidcurrencyvaluewithoutthe currencysymbolandmusthaveavaluegreaterthanorequaltozero." Operator="GreaterThanEqual"Type="Currency" ValueToCompare="0">*</asp:CompareValidator> </EditItemTemplate> <ItemTemplate> <asp:LabelID="Label4"runat="server" Text='<%#Bind("UnitPrice","{0:C}")%>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:TemplateFieldHeaderText="UnitsInStock"SortExpression="UnitsInStock"> <EditItemTemplate> <asp:TextBoxID="EditUnitsInStock"runat="server" Text='<%#Bind("UnitsInStock")%>'Columns="6"></asp:TextBox> <asp:CompareValidatorID="CompareValidator2"runat="server" ControlToValidate="EditUnitsInStock" ErrorMessage="Unitsinstockmustbeavalidnumber greaterthanorequaltozero." Operator="GreaterThanEqual"Type="Integer" ValueToCompare="0">*</asp:CompareValidator>
18 of28
</EditItemTemplate> <ItemTemplate> <asp:LabelID="Label5"runat="server" Text='<%#Bind("UnitsInStock","{0:N0}")%>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:TemplateFieldHeaderText="UnitsOnOrder"SortExpression="UnitsOnOrder"> <EditItemTemplate> <asp:TextBoxID="EditUnitsOnOrder"runat="server" Text='<%#Bind("UnitsOnOrder")%>'Columns="6"></asp:TextBox> <asp:CompareValidatorID="CompareValidator3"runat="server" ControlToValidate="EditUnitsOnOrder" ErrorMessage="Unitsonordermustbeavalidnumericvalue greaterthanorequaltozero." Operator="GreaterThanEqual"Type="Integer" ValueToCompare="0">*</asp:CompareValidator> </EditItemTemplate> <ItemTemplate> <asp:LabelID="Label6"runat="server" Text='<%#Bind("UnitsOnOrder","{0:N0}")%>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:TemplateFieldHeaderText="ReorderLevel"SortExpression="ReorderLevel"> <EditItemTemplate> <asp:TextBoxID="EditReorderLevel"runat="server" Text='<%#Bind("ReorderLevel")%>'Columns="6"></asp:TextBox> <asp:CompareValidatorID="CompareValidator4"runat="server" ControlToValidate="EditReorderLevel" ErrorMessage="Reorderlevelmustbeavalidnumericvalue greaterthanorequaltozero." Operator="GreaterThanEqual"Type="Integer" ValueToCompare="0">*</asp:CompareValidator> </EditItemTemplate> <ItemTemplate> <asp:LabelID="Label7"runat="server" Text='<%#Bind("ReorderLevel","{0:N0}")%>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:CheckBoxFieldDataField="Discontinued"HeaderText="Discontinued" SortExpression="Discontinued"/> </Columns> </asp:GridView>
PassingtheCorrectOriginalValuestothe ObjectDataSource
ThereareacoupleofproblemswiththewaytheGridViewhasbeenconfigured.IftheObjectDataSource's ConflictDetection propertyissettoCompareAllValues (asisours),whentheObjectDataSource's Update() or Delete() methodsareinvokedbytheGridView(orDetailsVieworFormView),theObjectDataSourceattemptsto copytheGridView'soriginalvaluesintoitsappropriateParameter instances.ReferbacktoFigure2fora graphicalrepresentationofthisprocess.
19 of28
Figure15:AttemptingtoDeleteAnyProductResultsinaFormatException
20 of28
RemovethecurrencyformattingfromtheItemTemplate.Thatis,insteadofusing <%#Bind("UnitPrice","{0:C}")%>,simplyuse<%#Bind("UnitPrice")%>.Thedownsideofthisis thatthepriceisnolongerformatted. DisplaytheUnitPrice formattedasacurrencyintheItemTemplate,butusetheEval keywordto accomplishthis.RecallthatEval performsonewaydatabinding.WestillneedtoprovidetheUnitPrice valuefortheoriginalvalues,sowe'llstillneedatwowaydatabindingstatementintheItemTemplate,but thiscanbeplacedinaLabelWebcontrolwhoseVisible propertyissettofalse.Wecouldusethe followingmarkupintheItemTemplate:
RemovethecurrencyformattingfromtheItemTemplate,using<%#Bind("UnitPrice")%>.Inthe GridView'sRowDataBound eventhandler,programmaticallyaccesstheLabelWebcontrolwithinwhichthe UnitPrice valueisdisplayedandsetits Text propertytotheformattedversion. LeavetheUnitPrice formattedasacurrency.IntheGridView's RowDeleting eventhandler,replacethe existingoriginalUnitPrice value($19.95)withanactualdecimalvalueusingDecimal.Parse.Wesaw howtoaccomplishsomethingsimilarintheRowUpdating eventhandlerintheHandlingBLL andDAL LevelExceptionsinanASP.NETPage tutorial.
21 of28
Figure16:TheObjectDataSourceCannotFindaMethodwiththeInputParametersitWantstoSend Lookingattheexception'smessage,it'sclearthattheObjectDataSourcewantstoinvokeaBLLDeleteProduct methodthatincludesoriginal_CategoryName andoriginal_SupplierName inputparameters.Thisisbecause theItemTemplatesfortheCategoryID andSupplierID TemplateFieldscurrentlycontaintwowayBind statementswiththeCategoryName andSupplierName datafields.Instead,weneedtoincludeBind statements withtheCategoryID andSupplierID datafields.Toaccomplishthis,replacetheexistingBindstatementswith Eval statements,andthenaddhiddenLabelcontrolswhoseText propertiesareboundtotheCategoryID and SupplierID datafieldsusingtwowaydatabinding,asshownbelow:
<asp:TemplateFieldHeaderText="Category"SortExpression="CategoryName"> <EditItemTemplate> ... </EditItemTemplate> <ItemTemplate> <asp:LabelID="DummyCategoryID"runat="server" Text='<%#Bind("CategoryID")%>'Visible="False"></asp:Label> <asp:LabelID="Label2"runat="server" Text='<%#Eval("CategoryName")%>'></asp:Label> </ItemTemplate> </asp:TemplateField> <asp:TemplateFieldHeaderText="Supplier"SortExpression="SupplierName"> <EditItemTemplate> ... </EditItemTemplate> <ItemTemplate> <asp:LabelID="DummySupplierID"runat="server" Text='<%#Bind("SupplierID")%>'Visible="False"></asp:Label> <asp:LabelID="Label3"runat="server" Text='<%#Eval("SupplierName")%>'></asp:Label> </ItemTemplate> </asp:TemplateField>
Step5:TestingtheOptimisticConcurrencySupport
Inordertoverifythatconcurrencyviolationsarebeingdetected(ratherthanresultingindatabeingblindly overwritten),weneedtoopentwobrowserwindowstothispage.Inbothbrowserinstances,clickontheEdit buttonforChai.Then,injustoneofthebrowsers,changethenameto"ChaiTea"andclickUpdate.Theupdate shouldsucceedandreturntheGridViewtoitspreeditingstate,with"ChaiTea"asthenewproductname. Intheotherbrowserwindowinstance,however,theproductnameTextBoxstillshows"Chai".Inthissecond browserwindow,updatetheUnitPrice to25.00.Withoutoptimisticconcurrencysupport,clickingupdateinthe secondbrowserinstancewouldchangetheproductnamebackto"Chai",therebyoverwritingthechangesmadeby thefirstbrowserinstance.Withoptimisticconcurrencyemployed,however,clickingtheUpdatebuttoninthe secondbrowserinstanceresultsinaDBConcurrencyException.
22 of28
Figure17:WhenaConcurrencyViolationisDetected,aDBConcurrencyException isThrown TheDBConcurrencyException isonlythrownwhentheDAL'sbatchupdatepatternisutilized.TheDBdirect patterndoesnotraiseanexception,itmerelyindicatesthatnorowswereaffected.Toillustratethis,returnboth browserinstances'GridViewtotheirpreeditingstate.Next,inthefirstbrowserinstance,clicktheEditbuttonand changetheproductnamefrom"ChaiTea"backto"Chai"andclickUpdate.Inthesecondbrowserwindow,click theDeletebuttonforChai. UponclickingDelete,thepagepostsback,theGridViewinvokestheObjectDataSource'sDelete() method,and theObjectDataSourcecallsdownintotheProductsOptimisticConcurrencyBLL class's DeleteProduct method, passingalongtheoriginalvalues.TheoriginalProductName valueforthesecondbrowserinstanceis"ChaiTea", whichdoesn'tmatchupwiththecurrentProductName valueinthedatabase.ThereforetheDELETE statement issuedtothedatabaseaffectszerorowssincethere'snorecordinthedatabasethattheWHERE clausesatisfies.The DeleteProduct methodreturnsfalse andtheObjectDataSource'sdataisreboundtotheGridView. Fromtheenduser'sperspective,clickingontheDeletebuttonforChaiTeainthesecondbrowserwindowcaused thescreentoflashand,uponcomingback,theproductisstillthere,althoughnowit'slistedas"Chai"(theproduct namechangemadebythefirstbrowserinstance).IftheuserclickstheDeletebuttonagain,theDeletewill succeed,astheGridView'soriginalProductName value("Chai")nowmatchesupwiththevalueinthedatabase. Inbothofthesecases,theuserexperienceisfarfromideal.Weclearlydon'twanttoshowtheuserthenittygritty detailsoftheDBConcurrencyException exceptionwhenusingthebatchupdatepattern.Andthebehaviorwhen usingtheDBdirectpatternissomewhatconfusingastheuserscommandfailed,buttherewasnopreciseindication ofwhy. Toremedythesetwoissues,wecancreateLabelWebcontrolsonthepagethatprovideanexplanationtowhyan updateordeletefailed.Forthebatchupdatepattern,wecandeterminewhetherornotaDBConcurrencyException exceptionoccurredintheGridView'spostleveleventhandler,displayingthewarninglabelasneeded.FortheDB directmethod,wecanexaminethereturnvalueoftheBLLmethod(whichistrue ifonerowwasaffected,false otherwise)anddisplayaninformationalmessageasneeded.
Figure18:TwoLabelControlsHaveBeenAddedtothePage
24 of28
HandlingConcurrencyViolationsWhenUpdating
Let'sfirstlookathowtohandleconcurrencyviolationswhenusingthebatchupdatepattern.Sincesuchviolations withthebatchupdatepatterncauseaDBConcurrencyException exceptiontobethrown,weneedtoaddcodeto ourASP.NETpagetodeterminewhetheraDBConcurrencyException exceptionoccurredduringtheupdate process.Ifso,weshoulddisplayamessagetotheuserexplainingthattheirchangeswerenotsavedbecause anotheruserhadmodifiedthesamedatabetweenwhentheystartededitingtherecordandwhentheyclickedthe Updatebutton. AswesawintheHandlingBLL andDALLevelExceptionsinanASP.NETPagetutorial,suchexceptionscanbe detectedandsuppressedinthedataWebcontrol'spostleveleventhandlers.Therefore,weneedtocreateanevent handlerfortheGridView'sRowUpdated eventthatchecksifaDBConcurrencyException exceptionhasbeen thrown.Thiseventhandlerispassedareferencetoanyexceptionthatwasraisedduringtheupdatingprocess,as shownintheeventhandlercodebelow:
protectedvoidProductsGrid_RowUpdated(objectsender,GridViewUpdatedEventArgse) { if(e.Exception!=null&&e.Exception.InnerException!=null) { if(e.Exception.InnerExceptionisSystem.Data.DBConcurrencyException) { //Displaythewarningmessageandnotethatthe //exceptionhasbeenhandled... UpdateConflictMessage.Visible=true e.ExceptionHandled=true } } }
25 of28
Figure19:AUser'sUpdatesareLostintheFaceofaConcurrencyViolation Note: Alternatively,ratherthanreturningtheGridViewtothepreeditingstate,wecouldleavetheGridViewinits editingstatebysettingtheKeepInEditMode propertyofthepassedinGridViewUpdatedEventArgs objecttotrue. Ifyoutakethisapproach,however,becertaintorebindthedatatotheGridView(byinvokingitsDataBind() method)sothattheotheruser'svaluesareloadedintotheeditinginterface.Thecodeavailablefordownloadwith thistutorialhasthesetwolinesofcodeintheRowUpdated eventhandlercommentedoutsimplyuncommentthese linesofcodetohavetheGridViewremainineditmodeafteraconcurrencyviolation.
RespondingtoConcurrencyViolationsWhen Deleting
WiththeDBdirectpattern,thereisnoexceptionraisedinthefaceofaconcurrencyviolation.Instead,thedatabase statementsimplyaffectsnorecords,astheWHEREclausedoesnotmatchwithanyrecord.Allofthedata modificationmethodscreatedintheBLLhavebeendesignedsuchthattheyreturnaBooleanvalueindicating whetherornottheyaffectedpreciselyonerecord.Therefore,todetermineifaconcurrencyviolationoccurred whendeletingarecord,wecanexaminethereturnvalueoftheBLL'sDeleteProduct method. ThereturnvalueforaBLLmethodcanbeexaminedintheObjectDataSource'spostleveleventhandlersthrough theReturnValue propertyoftheObjectDataSourceStatusEventArgs objectpassedintotheeventhandler.Since weareinterestedindeterminingthereturnvaluefromtheDeleteProduct method,weneedtocreateanevent handlerfortheObjectDataSource'sDeleted event.TheReturnValue propertyisoftypeobject andcanbenull ifanexceptionwasraisedandthemethodwasinterruptedbeforeitcouldreturnavalue.Therefore,weshouldfirst ensurethattheReturnValue propertyisnotnull andisaBooleanvalue.Assumingthischeckpasses,weshow theDeleteConflictMessage LabelcontroliftheReturnValue isfalse.Thiscanbeaccomplishedbyusingthe
26 of28
followingcode:
protectedvoidProductsOptimisticConcurrencyDataSource_Deleted( objectsender,ObjectDataSourceStatusEventArgse) { if(e.ReturnValue!=null&&e.ReturnValueisbool) { booldeleteReturnValue=(bool)e.ReturnValue if(deleteReturnValue==false) { //Norowwasdeleted,displaythewarningmessage DeleteConflictMessage.Visible=true } } }
Figure20:AUser'sDeleteisCanceledintheFaceofaConcurrencyViolation
Summary
Opportunitiesforconcurrencyviolationsexistineveryapplicationthatallowsmultiple,concurrentuserstoupdate ordeletedata.Ifsuchviolationsarenotaccountedfor,whentwouserssimultaneouslyupdatethesamedata whoevergetsinthelastwrite"wins,"overwritingtheotheruser'schangeschanges.Alternatively,developerscan implementeitheroptimisticorpessimisticconcurrencycontrol.Optimisticconcurrencycontrolassumesthat concurrencyviolationsareinfrequentandsimplydisallowsanupdateordeletecommandthatwouldconstitutea concurrencyviolation.Pessimisticconcurrencycontrolassumesthatconcurrencyviolationsarefrequentand simplyrejectingoneuser'supdateordeletecommandisnotacceptable.Withpessimisticconcurrencycontrol, updatingarecordinvolveslockingit,therebypreventinganyotherusersfrommodifyingordeletingtherecord 27 of28
whileitislocked. TheTypedDataSetin.NETprovidesfunctionalityforsupportingoptimisticconcurrencycontrol.Inparticular,the UPDATE andDELETE statementsissuedtothedatabaseincludeallofthetable'scolumns,therebyensuringthatthe updateordeletewillonlyoccuriftherecord'scurrentdatamatcheswiththeoriginaldatatheuserhadwhen performingtheirupdateordelete.OncetheDALhasbeenconfiguredtosupportoptimisticconcurrency,theBLL methodsneedtobeupdated.Additionally,theASP.NETpagethatcallsdownintotheBLLmustbeconfigured suchthattheObjectDataSourceretrievestheoriginalvaluesfromitsdataWebcontrolandpassesthemdowninto theBLL. Aswesawinthistutorial,implementingoptimisticconcurrencycontrolinanASP.NETwebapplicationinvolves updatingtheDALandBLLandaddingsupportintheASP.NETpage.Whetherornotthisaddedworkisawise investmentofyourtimeandeffortdependsonyourapplication.Ifyouinfrequentlyhaveconcurrentusersupdating data,orthedatatheyareupdatingisdifferentfromoneanother,thenconcurrencycontrolisnotakeyissue.If, however,youroutinelyhavemultipleusersonyoursiteworkingwiththesamedata,concurrencycontrolcanhelp preventoneuser'supdatesordeletesfromunwittinglyoverwritinganother's. HappyProgramming!
AbouttheAuthor
ScottMitchell,authorofsixASP/ASP.NETbooksandfounderof4GuysFromRolla.com,hasbeenworkingwith MicrosoftWebtechnologiessince1998.Scottworksasanindependentconsultant,trainer,andwriter,recently completinghislatestbook, SamsTeachYourselfASP.NET2.0in24Hours.Hecanbereachedat mitchell@4guysfromrolla.comorviahisblog,whichcanbefoundathttp://ScottOnWriting.NET.
28 of28