You are on page 1of 28

Thistutorialispartofaset.FindoutmoreaboutdataaccesswithASP.NETintheWorkingwithData inASP.NET2.0sectionoftheASP.NETsiteathttp://www.asp.net/learn/dataaccess/default.aspx.

WorkingwithDatainASP.NET2.0::Implementing OptimisticConcurrency Introduction


Forwebapplicationsthatonlyallowuserstoviewdata,orforthosethatincludeonlyasingleuserwhocanmodify data,there'snothreatoftwoconcurrentusersaccidentallyoverwritingoneanother'schanges.Forwebapplications thatallowmultipleuserstoupdateordeletedata,however,there'sthepotentialforoneuser'smodificationsto clashwithanotherconcurrentuser's.Withoutanyconcurrencypolicyinplace,whentwousersaresimultaneously editingasinglerecord,theuserwhocommitsherchangeslastwilloverridethechangesmadebythefirst. Forexample,imaginethattwousers,JisunandSam,werebothvisitingapageinourapplicationthatallowed visitorstoupdateanddeletetheproductsthroughaGridViewcontrol.BothclicktheEditbuttonintheGridView aroundthesametime.Jisunchangestheproductnameto"ChaiTea"andclickstheUpdatebutton.Thenetresultis anUPDATE statementthatissenttothedatabase,whichsetsalloftheproduct'supdateablefields(eventhoughJisun onlyupdatedonefield,ProductName).Atthispointintime,thedatabasehasthevalues"ChaiTea,"thecategory Beverages,thesupplierExoticLiquids,andsoonforthisparticularproduct.However,theGridViewonSam's screenstillshowstheproductnameintheeditableGridViewrowas"Chai".AfewsecondsafterJisun'schanges havebeencommitted,SamupdatesthecategorytoCondimentsandclicksUpdate.ThisresultsinanUPDATE statementsenttothedatabasethatsetstheproductnameto"Chai,"theCategoryID tothecorresponding BeveragescategoryID,andsoon.Jisun'schangestotheproductnamehavebeenoverwritten.Figure1graphically depictsthisseriesofevents.

Figure1:WhenTwoUsersSimultaneouslyUpdateaRecordThere'sPotentialforOneUser'sChangesto OverwritetheOther's Similarly,whentwousersarevisitingapage,oneusermightbeinthemidstofupdatingarecordwhenitisdeleted byanotheruser.Or,betweenwhenauserloadsapageandwhentheyclicktheDeletebutton,anotherusermay

1 of28

havemodifiedthecontentsofthatrecord. Therearethreeconcurrencycontrol strategiesavailable:


l

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

Figure3:ConnecttotheSameNorthwindDatabase Next,wearepromptedastohowtoquerythedata:throughanadhocSQLstatement,anewstoredprocedure,or anexistingstoredprocedure.SinceweusedadhocSQLqueriesinouroriginalDAL,usethisoptionhereaswell.

Figure4:SpecifytheDatatoRetrieveUsinganAdHocSQLStatement Onthefollowingscreen,entertheSQLquerytousetoretrievetheproductinformation.Let'susetheexactsame SQLqueryusedfortheProducts TableAdapterfromouroriginalDAL,whichreturnsalloftheProduct columns alongwiththeproduct'ssupplierandcategorynames:


SELECTProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit, UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued, (SELECTCategoryNameFROMCategories WHERECategories.CategoryID=Products.CategoryID) asCategoryName, (SELECTCompanyNameFROMSuppliers WHERESuppliers.SupplierID=Products.SupplierID) asSupplierName FROMProducts

5 of28

Figure5:UsetheSameSQLQueryfromtheProducts TableAdapterintheOriginalDAL Beforemovingontothenextscreen,clicktheAdvancedOptionsbutton.TohavethisTableAdapteremploy optimisticconcurrencycontrol,simplycheckthe"Useoptimisticconcurrency"checkbox.

Figure6:EnableOptimisticConcurrencyControlbyCheckingthe"Useoptimisticconcurrency"CheckBox Lastly,indicatethattheTableAdaptershouldusethedataaccesspatternsthatbothfillaDataTableandreturna DataTablealsoindicatethattheDBdirectmethodsshouldbecreated.ChangethemethodnamefortheReturna DataTablepatternfromGetDatatoGetProducts,soastomirrorthenamingconventionsweusedinouroriginal DAL.

6 of28

Figure7:HavetheTableAdapterUtilizeAllDataAccessPatterns Aftercompletingthewizard,theDataSetDesignerwillincludeastronglytypedProducts DataTableand TableAdapter.TakeamomenttorenametheDataTablefromProducts toProductsOptimisticConcurrency, whichyoucandobyrightclickingontheDataTable'stitlebarandchoosingRenamefromthecontextmenu.

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

Figure9:MarktheQueryTypeasa"SELECT whichreturnsrows" Onthenextscreenwe'repromptedfortheSQLquerytouse,withtheTableAdapter'sdefaultquerypreloaded. AugmenttheexistingquerytoincludetheclauseWHEREProductID=@ProductID,asshowninFigure10.

Figure10:AddaWHERE ClausetothePreLoadedQuerytoReturnaSpecificProductRecord Finally,changethegeneratedmethodnamestoFillByProductID andGetProductByProductID.

9 of28

Figure11:RenametheMethodstoFillByProductID andGetProductByProductID Withthiswizardcomplete,theTableAdapternowcontainstwomethodsforretrievingdata:GetProducts(), whichreturnsallproductsandGetProductByProductID(productID),whichreturnsthespecifiedproduct.

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

Figure12:AddtheProductsOptimisticConcurrencyBLL ClasstotheBLLFolder Next,addthefollowingcodetotheProductsOptimisticConcurrencyBLL class:


usingSystem usingSystem.Data usingSystem.Configuration usingSystem.Web usingSystem.Web.Security usingSystem.Web.UI usingSystem.Web.UI.WebControls usingSystem.Web.UI.WebControls.WebParts usingSystem.Web.UI.HtmlControls usingNorthwindOptimisticConcurrencyTableAdapters

[System.ComponentModel.DataObject] publicclassProductsOptimisticConcurrencyBLL { privateProductsOptimisticConcurrencyTableAdapter_productsAdapter=null protectedProductsOptimisticConcurrencyTableAdapterAdapter { get { if(_productsAdapter==null) _productsAdapter=newProductsOptimisticConcurrencyTableAdapter() return_productsAdapter } } [System.ComponentModel.DataObjectMethodAttribute

11 of28

(System.ComponentModel.DataObjectMethodType.Select,true)] publicNorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTableGetProducts() { returnAdapter.GetProducts() } }

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

quantityPerUnit,unitPrice,unitsInStock,unitsOnOrder,reorderLevel, discontinued) //STEP5:Updatetheproductrecord introwsAffected=Adapter.Update(product) //Returntrueifpreciselyonerowwasupdated,otherwisefalse returnrowsAffected==1 }

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.

Figure13:HavetheObjectDataSourceUsetheProductsOptimisticConcurrencyBLL Object ChoosetheGetProducts,UpdateProduct,andDeleteProduct methodsfromdropdownlistsinthewizard.For theUpdateProductmethod,usetheoverloadthatacceptsalloftheproduct'sdatafields.

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

There'sonefinalpropertysettingthatneedstobemadeinorderfortheObjectDataSourcetocorrectlypassthe originalvaluestotheBLLmethods.TheObjectDataSourcehasaConflictDetectionproperty thatcanbeassigned tooneoftwovalues:


l

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>

We'reveryclosetohavingafullyworkingexample.However,thereareafewsubtletiesthatwillcreepupand causeusproblems.Additionally,westillneedsomeinterfacethatalertstheuserwhenaconcurrencyviolationhas occurred. Note: InorderforadataWebcontroltocorrectlypasstheoriginalvaluestotheObjectDataSource(whicharethen passedtotheBLL),it'svitalthattheGridView'sEnableViewState propertyissettotrue (thedefault).Ifyou disableviewstate,theoriginalvaluesarelostonpostback.

PassingtheCorrectOriginalValuestothe ObjectDataSource
ThereareacoupleofproblemswiththewaytheGridViewhasbeenconfigured.IftheObjectDataSource's ConflictDetection propertyissettoCompareAllValues (asisours),whentheObjectDataSource's Update() or Delete() methodsareinvokedbytheGridView(orDetailsVieworFormView),theObjectDataSourceattemptsto copytheGridView'soriginalvaluesintoitsappropriateParameter instances.ReferbacktoFigure2fora graphicalrepresentationofthisprocess.

19 of28

Specifically,theGridView'soriginalvaluesareassignedthevaluesinthetwowaydatabindingstatementseach timethedataisboundtotheGridView.Therefore,it'sessentialthattherequiredoriginalvaluesallarecapturedvia twowaydatabindingandthattheyareprovidedinaconvertibleformat. Toseewhythisisimportant,takeamomenttovisitourpageinabrowser.Asexpected,theGridViewlistseach productwithanEditandDeletebuttonintheleftmostcolumn.

Figure14:TheProductsareListedinaGridView IfyouclicktheDeletebuttonforanyproduct,aFormatException isthrown.

Figure15:AttemptingtoDeleteAnyProductResultsinaFormatException

20 of28

TheFormatException israisedwhentheObjectDataSourceattemptstoreadintheoriginalUnitPrice value. SincetheItemTemplate hastheUnitPrice formattedasacurrency(<%#Bind("UnitPrice","{0:C}")%>),it includesacurrencysymbol,like$19.95.TheFormatException occursastheObjectDataSourceattemptsto convertthisstringintoadecimal.Tocircumventthisproblem,wehaveanumberofoptions:


l

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:

<ItemTemplate> <asp:LabelID="DummyUnitPrice"runat="server" Text='<%#Bind("UnitPrice")%>'Visible="false"></asp:Label> <asp:LabelID="Label4"runat="server" Text='<%#Eval("UnitPrice","{0:C}")%>'></asp:Label> </ItemTemplate>


l

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.

FormyexampleIchosetogowiththesecondapproach,addingahiddenLabelWebcontrolwhoseText property istwowaydataboundtotheunformattedUnitPrice value. Aftersolvingthisproblem,tryclickingtheDeletebuttonforanyproductagain.Thistimeyou'llgetan InvalidOperationException whentheObjectDataSourceattemptstoinvoketheBLL'sUpdateProduct method.

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>

Withthesechanges,wearenowabletosuccessfullydeleteandeditproductinformation!InStep5we'lllookat howtoverifythatconcurrencyviolationsarebeingdetected.Butfornow,takeafewminutestotryupdatingand deletingafewrecordstoensurethatupdatinganddeletingforasingleuserworksasexpected.

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.

Step6:AddingInformationalMessagesand DisplayingThemintheFaceofaConcurrency Violation


Whenaconcurrencyviolationoccurs,thebehaviorexhibiteddependsonwhethertheDAL'sbatchupdateorDB 23 of28

directpatternwasused.Ourtutorialusesbothpatterns,withthebatchupdatepatternbeingusedforupdatingand theDBdirectpatternusedfordeleting.Togetstarted,let'saddtwoLabelWebcontrolstoourpagethatexplain thataconcurrencyviolationoccurredwhenattemptingtodeleteorupdatedata.SettheLabelcontrol'sVisible andEnableViewState propertiestofalsethiswillcausethemtobehiddenoneachpagevisitexceptforthose particularpagevisitswheretheirVisible propertyisprogrammaticallysettotrue.


<asp:LabelID="DeleteConflictMessage"runat="server"Visible="False" EnableViewState="False"CssClass="Warning" Text="Therecordyouattemptedtodeletehasbeenmodifiedbyanotheruser sinceyoulastvisitedthispage.Yourdeletewascancelledtoallow youtoreviewtheotheruser'schangesanddetermineifyouwantto continuedeletingthisrecord."/> <asp:LabelID="UpdateConflictMessage"runat="server"Visible="False" EnableViewState="False"CssClass="Warning" Text="Therecordyouattemptedtoupdatehasbeenmodifiedbyanotheruser sinceyoustartedtheupdateprocess.Yourchangeshavebeenreplaced withthecurrentvalues.Pleasereviewtheexistingvaluesandmake anyneededchanges."/>

InadditiontosettingtheirVisible,EnabledViewState,andText properties,I'vealsosettheCssClass property toWarning,whichcausestheLabel'stobedisplayedinalarge,red,italic,boldfont.ThisCSSWarning classwas definedandaddedtoStyles.cssbackintheExaminingtheEventsAssociatedwithInserting,Updating,and Deleting tutorial. AfteraddingtheseLabels,theDesignerinVisualStudioshouldlooksimilartoFigure18.

Figure18:TwoLabelControlsHaveBeenAddedtothePage

24 of28

WiththeseLabelWebcontrolsinplace,we'rereadytoexaminehowtodeterminewhenaconcurrencyviolation hasoccurred,atwhichpointtheappropriateLabel'sVisible propertycanbesettotrue,displayingthe informationalmessage.

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 } } }

InthefaceofaDBConcurrencyException exception,thiseventhandlerdisplaystheUpdateConflictMessage Labelcontrolandindicatesthattheexceptionhasbeenhandled.Withthiscodeinplace,whenaconcurrency violationoccurswhenupdatingarecord,theuser'schangesarelost,sincetheywouldhaveoverwrittenanother user'smodificationsatthesametime.Inparticular,theGridViewisreturnedtoitspreeditingstateandboundto thecurrentdatabasedata.ThiswillupdatetheGridViewrowwiththeotheruser'schanges,whichwerepreviously notvisible.Additionally,theUpdateConflictMessage Labelcontrolwillexplaintotheuserwhatjusthappened. ThissequenceofeventsisdetailedinFigure19.

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 } } }

Inthefaceofaconcurrencyviolation,theuser'sdeleterequestiscanceled.TheGridViewisrefreshed,showingthe changesthatoccurredforthatrecordbetweenthetimetheuserloadedthepageandwhenheclickedtheDelete button.Whensuchaviolationtranspires,theDeleteConflictMessage Labelisshown,explainingwhatjust happened(seeFigure20).

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

You might also like