使用EF6和MVC5实现一个简单的选课系统--使用EF6处理并发操作(10/12)
2014-03-24 20:59
876 查看
先贴出来英文的,抽空翻译!
本讲源代码下载
TheContosoUniversitysamplewebapplicationdemonstrateshowtocreateASP.NETMVC5applicationsusingtheEntityFramework6CodeFirstandVisualStudio2013.Forinformationaboutthetutorialseries,seethe
firsttutorialintheseries.
Inearliertutorialsyoulearnedhowtoupdatedata.Thistutorialshowshowtohandleconflictswhenmultipleusersupdatethesameentityatthesametime.
You'llchangethewebpagesthatworkwiththe
sothattheyhandleconcurrencyerrors.ThefollowingillustrationsshowtheIndexandDeletepages,includingsomemessagesthataredisplayedifaconcurrencyconflictoccurs.
Aconcurrencyconflictoccurswhenoneuserdisplaysanentity'sdatainordertoeditit,andthenanotheruserupdatesthesameentity'sdatabeforethefirstuser'schangeiswrittentothedatabase.Ifyoudon'tenablethedetectionofsuchconflicts,whoever
updatesthedatabaselastoverwritestheotheruser'schanges.Inmanyapplications,thisriskisacceptable:iftherearefewusers,orfewupdates,orifisn'treallycriticalifsomechangesareoverwritten,thecostofprogrammingforconcurrencymight
outweighthebenefit.Inthatcase,youdon'thavetoconfiguretheapplicationtohandleconcurrencyconflicts.
Ifyourapplicationdoesneedtopreventaccidentaldatalossinconcurrencyscenarios,onewaytodothatistousedatabaselocks.Thisiscalledpessimistic
concurrency.Forexample,beforeyoureadarowfromadatabase,yourequestalockforread-onlyorforupdateaccess.Ifyoulockarowforupdateaccess,nootherusersareallowedtolocktheroweitherforread-onlyorupdateaccess,becausethey
wouldgetacopyofdatathat'sintheprocessofbeingchanged.Ifyoulockarowforread-onlyaccess,otherscanalsolockitforread-onlyaccessbutnotforupdate.
Managinglockshasdisadvantages.Itcanbecomplextoprogram.Itrequiressignificantdatabasemanagementresources,anditcancauseperformanceproblemsasthenumberofusersofanapplicationincreases.Forthesereasons,notalldatabasemanagementsystems
supportpessimisticconcurrency.TheEntityFrameworkprovidesnobuilt-insupportforit,andthistutorialdoesn'tshowyouhowtoimplementit.
Thealternativetopessimisticconcurrencyisoptimisticconcurrency.Optimisticconcurrencymeansallowingconcurrency
conflictstohappen,andthenreactingappropriatelyiftheydo.Forexample,JohnrunstheDepartmentsEditpage,changestheBudgetamount
fortheEnglishdepartmentfrom$350,000.00to$0.00.
BeforeJohnclicksSave,JanerunsthesamepageandchangestheStart
Datefieldfrom9/1/2007to8/8/2013.
JohnclicksSavefirstandseeshischangewhenthebrowserreturnstotheIndexpage,thenJaneclicksSave.
Whathappensnextisdeterminedbyhowyouhandleconcurrencyconflicts.Someoftheoptionsincludethefollowing:
Youcankeeptrackofwhichpropertyauserhasmodifiedandupdateonlythecorrespondingcolumnsinthedatabase.Intheexamplescenario,nodatawouldbelost,becausedifferentpropertieswereupdatedbythetwousers.Thenexttimesomeonebrowsesthe
Englishdepartment,they'llseebothJohn'sandJane'schanges—astartdateof8/8/2013andabudgetofZerodollars.
Thismethodofupdatingcanreducethenumberofconflictsthatcouldresultindataloss,butitcan'tavoiddatalossifcompetingchangesaremadetothesamepropertyofanentity.WhethertheEntityFrameworkworksthiswaydependsonhowyouimplement
yourupdatecode.It'softennotpracticalinawebapplication,becauseitcanrequirethatyoumaintainlargeamountsofstateinordertokeeptrackofalloriginalpropertyvaluesforanentityaswellasnewvalues.Maintaininglargeamountsofstate
canaffectapplicationperformancebecauseiteitherrequiresserverresourcesormustbeincludedinthewebpageitself(forexample,inhiddenfields)orinacookie.
YoucanletJane'schangeoverwriteJohn'schange.ThenexttimesomeonebrowsestheEnglishdepartment,they'llsee8/8/2013andtherestored$350,000.00value.ThisiscalledaClient
WinsorLastinWinsscenario.(Allvaluesfromtheclienttakeprecedenceoverwhat'sinthedatastore.)As
notedintheintroductiontothissection,ifyoudon'tdoanycodingforconcurrencyhandling,thiswillhappenautomatically.
YoucanpreventJane'schangefrombeingupdatedinthedatabase.Typically,youwoulddisplayanerrormessage,showherthecurrentstateofthedata,andallowhertoreapplyherchangesifshestillwantstomakethem.ThisiscalledaStore
Winsscenario.(Thedata-storevaluestakeprecedenceoverthevaluessubmittedbytheclient.)You'llimplementtheStoreWinsscenariointhistutorial.Thismethodensuresthatnochangesareoverwrittenwithoutauserbeingalertedtowhat'shappening.
YoucanresolveconflictsbyhandlingOptimisticConcurrencyExceptionexceptions
thattheEntityFrameworkthrows.Inordertoknowwhentothrowtheseexceptions,theEntityFrameworkmustbeabletodetectconflicts.Therefore,youmustconfigurethedatabaseandthedatamodelappropriately.Someoptionsforenablingconflictdetection
includethefollowing:
Inthedatabasetable,includeatrackingcolumnthatcanbeusedtodeterminewhenarowhasbeenchanged.YoucanthenconfiguretheEntityFrameworktoincludethatcolumninthe
ofSQL
Thedatatypeofthetrackingcolumnistypicallyrowversionrowversionvalue
isasequentialnumberthat'sincrementedeachtimetherowisupdated.Inan
the
(theoriginalrowversion).Iftherowbeingupdatedhasbeenchangedbyanotheruser,thevalueinthe
isdifferentthantheoriginalvalue,sothe
can'tfindtherowtoupdatebecauseofthe
theEntityFrameworkfindsthatnorowshavebeenupdatedbythe
(thatis,whenthenumberofaffectedrowsiszero),itinterpretsthatasaconcurrencyconflict.
ConfiguretheEntityFrameworktoincludetheoriginalvaluesofeverycolumninthetableinthe
of
Asinthefirstoption,ifanythingintherowhaschangedsincetherowwasfirstread,the
won'treturnarowtoupdate,whichtheEntityFrameworkinterpretsasaconcurrencyconflict.Fordatabasetablesthathavemanycolumns,thisapproachcanresultinverylarge
andcanrequirethatyoumaintainlargeamountsofstate.Asnotedearlier,maintaininglargeamountsofstatecanaffectapplicationperformance.Thereforethisapproachisgenerallynotrecommended,anditisn'tthemethodusedinthistutorial.
Ifyoudowanttoimplementthisapproachtoconcurrency,youhavetomarkallnon-primary-keypropertiesintheentityyouwanttotrackconcurrencyforbyaddingtheConcurrencyCheckattribute
tothem.ThatchangeenablestheEntityFrameworktoincludeallcolumnsintheSQL
of
Intheremainderofthistutorialyou'lladdarowversiontracking
propertytothe
andtesttoverifythateverythingworkscorrectly.
InModels\Department.cs,addatrackingpropertynamed
TheTimestampattribute
specifiesthatthiscolumnwillbeincludedinthe
of
senttothedatabase.TheattributeiscalledTimestampbecause
previousversionsofSQLServerusedaSQLtimestampdata
typebeforetheSQLrowversionreplaced
it.The.Nettypeforrowversionis
abytearray.
IfyouprefertousethefluentAPI,youcanusetheIsConcurrencyTokenmethod
tospecifythetrackingproperty,asshowninthefollowingexample:
Byaddingapropertyyouchangedthedatabasemodel,soyouneedtodoanothermigration.InthePackageManagerConsole(PMC),enterthefollowingcommands:
InControllers\DepartmentController.cs,adda
IntheDepartmentController.csfile,changeallfouroccurrencesof"LastName"to"FullName"sothatthedepartment
administratordrop-downlistswillcontainthefullnameoftheinstructorratherthanjustthelastname.
Replacetheexistingcodeforthe
withthefollowingcode:
Theviewwillstoretheoriginal
Whenthemodelbindercreatesthe
willhavetheoriginal
fortheotherproperties,asenteredbytheuserontheEditpage.ThenwhentheEntityFrameworkcreatesaSQL
thatcommandwillincludea
thathastheoriginal
Ifnorowsareaffectedbythe
original
andthecodeinthe
fromtheexceptionobject.
Thisobjecthasthenewvaluesenteredbytheuserinits
andyoucangetthevaluesreadfromthedatabasebycallingthe
The
therowfromthedatabase;otherwise,youhavetocasttheobjectreturnedtothe
inordertoaccessthe
Next,thecodeaddsacustomerrormessageforeachcolumnthathasdatabasevaluesdifferentfromwhattheuserenteredontheEditpage:
Alongererrormessageexplainswhathappenedandwhattodoaboutit:
Finally,thecodesetsthe
tothenewvalueretrievedfromthedatabase.Thisnew
willbestoredinthehiddenfieldwhentheEditpageisredisplayed,andthenexttimetheuserclicksSave,only
concurrencyerrorsthathappensincetheredisplayoftheEditpagewillbecaught.
InViews\Department\Edit.cshtml,addahiddenfieldtosavethe
value,immediatelyfollowingthehiddenfieldforthe
RunthesiteandclickDepartments:
RightclicktheEdithyperlinkfortheEnglishdepartmentandselectOpen
innewtab,thenclicktheEdithyperlinkfortheEnglishdepartment.Thetwotabsdisplaythesameinformation.
ChangeafieldinthefirstbrowsertabandclickSave.
ThebrowsershowstheIndexpagewiththechangedvalue.
ChangeafieldinthesecondbrowsertabandclickSave.
ClickSaveinthesecondbrowsertab.Youseeanerrormessage:
ClickSaveagain.Thevalueyouenteredinthesecondbrowsertabissavedalongwiththeoriginalvalueofthedata
youchangedinthefirstbrowser.YouseethesavedvalueswhentheIndexpageappears.
FortheDeletepage,theEntityFrameworkdetectsconcurrencyconflictscausedbysomeoneelseeditingthedepartmentinasimilarmanner.Whenthe
displaystheconfirmationview,theviewincludestheoriginal
inahiddenfield.Thatvalueisthenavailabletothe
that'scalledwhentheuserconfirmsthedeletion.WhentheEntityFrameworkcreatestheSQL
itincludesa
Ifthecommandresultsinzerorowsaffected(meaningtherowwaschangedaftertheDeleteconfirmationpagewasdisplayed),aconcurrencyexceptionisthrown,andthe
ordertoredisplaytheconfirmationpagewithanerrormessage.It'salsopossiblethatzerorowswereaffectedbecausetherowwasdeletedbyanotheruser,sointhatcaseadifferenterrormessageisdisplayed.
InDepartmentController.cs,replacethe
withthefollowingcode:
Themethodacceptsanoptionalparameterthatindicateswhetherthepageisbeingredisplayedafteraconcurrencyerror.Ifthisflagis
anerrormessageissenttotheviewusinga
Replacethecodeinthe
(named
Inthescaffoldedcodethatyoujustreplaced,thismethodacceptedonlyarecordID:
You'vechangedthisparametertoa
bythemodelbinder.Thisgivesyouaccesstothe
valueinadditiontotherecordkey.
Youhavealsochangedtheactionmethodnamefrom
Thescaffoldedcodenamedthe
givethe
overloadedmethodstohavedifferentmethodparameters.)Nowthatthesignaturesareunique,youcanstickwiththeMVCconventionandusethesamenameforthe
methods.
Ifaconcurrencyerroriscaught,thecoderedisplaystheDeleteconfirmationpageandprovidesaflagthatindicatesitshoulddisplayaconcurrencyerrormessage.
InViews\Department\Delete.cshtml,replacethescaffoldedcodewiththefollowingcodethataddsanerrormessagefield
andhiddenfieldsfortheDepartmentIDandRowVersionproperties.Thechangesarehighlighted.
Thiscodeaddsanerrormessagebetweenthe
Itreplaces
the
Finally,itaddshiddenfieldsforthe
afterthe
RuntheDepartmentsIndexpage.RightclicktheDeletehyperlinkfortheEnglishdepartmentandselectOpen
innewtab,theninthefirsttabclicktheEdithyperlinkfortheEnglishdepartment.
Inthefirstwindow,changeoneofthevalues,andclickSave:
TheIndexpageconfirmsthechange.
Inthesecondtab,clickDelete.
Youseetheconcurrencyerrormessage,andtheDepartmentvaluesarerefreshedwithwhat'scurrentlyinthedatabase.
IfyouclickDeleteagain,you'reredirectedtotheIndexpage,whichshowsthatthedepartmenthasbeendeleted.
Thiscompletestheintroductiontohandlingconcurrencyconflicts.Forinformationaboutotherwaystohandlevariousconcurrencyscenarios,seeOptimistic
ConcurrencyPatternsandWorking
withPropertyValuesonMSDN.Thenexttutorialshowshowtoimplementtable-per-hierarchyinheritanceforthe
LinkstootherEntityFrameworkresourcescanbefoundintheASP.NET
DataAccess-RecommendedResources.
本讲源代码下载
TheContosoUniversitysamplewebapplicationdemonstrateshowtocreateASP.NETMVC5applicationsusingtheEntityFramework6CodeFirstandVisualStudio2013.Forinformationaboutthetutorialseries,seethe
firsttutorialintheseries.
Inearliertutorialsyoulearnedhowtoupdatedata.Thistutorialshowshowtohandleconflictswhenmultipleusersupdatethesameentityatthesametime.
You'llchangethewebpagesthatworkwiththe
Departmententity
sothattheyhandleconcurrencyerrors.ThefollowingillustrationsshowtheIndexandDeletepages,includingsomemessagesthataredisplayedifaconcurrencyconflictoccurs.
ConcurrencyConflicts
Aconcurrencyconflictoccurswhenoneuserdisplaysanentity'sdatainordertoeditit,andthenanotheruserupdatesthesameentity'sdatabeforethefirstuser'schangeiswrittentothedatabase.Ifyoudon'tenablethedetectionofsuchconflicts,whoeverupdatesthedatabaselastoverwritestheotheruser'schanges.Inmanyapplications,thisriskisacceptable:iftherearefewusers,orfewupdates,orifisn'treallycriticalifsomechangesareoverwritten,thecostofprogrammingforconcurrencymight
outweighthebenefit.Inthatcase,youdon'thavetoconfiguretheapplicationtohandleconcurrencyconflicts.
PessimisticConcurrency(Locking)
Ifyourapplicationdoesneedtopreventaccidentaldatalossinconcurrencyscenarios,onewaytodothatistousedatabaselocks.Thisiscalledpessimisticconcurrency.Forexample,beforeyoureadarowfromadatabase,yourequestalockforread-onlyorforupdateaccess.Ifyoulockarowforupdateaccess,nootherusersareallowedtolocktheroweitherforread-onlyorupdateaccess,becausethey
wouldgetacopyofdatathat'sintheprocessofbeingchanged.Ifyoulockarowforread-onlyaccess,otherscanalsolockitforread-onlyaccessbutnotforupdate.
Managinglockshasdisadvantages.Itcanbecomplextoprogram.Itrequiressignificantdatabasemanagementresources,anditcancauseperformanceproblemsasthenumberofusersofanapplicationincreases.Forthesereasons,notalldatabasemanagementsystems
supportpessimisticconcurrency.TheEntityFrameworkprovidesnobuilt-insupportforit,andthistutorialdoesn'tshowyouhowtoimplementit.
OptimisticConcurrency
Thealternativetopessimisticconcurrencyisoptimisticconcurrency.Optimisticconcurrencymeansallowingconcurrencyconflictstohappen,andthenreactingappropriatelyiftheydo.Forexample,JohnrunstheDepartmentsEditpage,changestheBudgetamount
fortheEnglishdepartmentfrom$350,000.00to$0.00.
BeforeJohnclicksSave,JanerunsthesamepageandchangestheStart
Datefieldfrom9/1/2007to8/8/2013.
JohnclicksSavefirstandseeshischangewhenthebrowserreturnstotheIndexpage,thenJaneclicksSave.
Whathappensnextisdeterminedbyhowyouhandleconcurrencyconflicts.Someoftheoptionsincludethefollowing:
Youcankeeptrackofwhichpropertyauserhasmodifiedandupdateonlythecorrespondingcolumnsinthedatabase.Intheexamplescenario,nodatawouldbelost,becausedifferentpropertieswereupdatedbythetwousers.Thenexttimesomeonebrowsesthe
Englishdepartment,they'llseebothJohn'sandJane'schanges—astartdateof8/8/2013andabudgetofZerodollars.
Thismethodofupdatingcanreducethenumberofconflictsthatcouldresultindataloss,butitcan'tavoiddatalossifcompetingchangesaremadetothesamepropertyofanentity.WhethertheEntityFrameworkworksthiswaydependsonhowyouimplement
yourupdatecode.It'softennotpracticalinawebapplication,becauseitcanrequirethatyoumaintainlargeamountsofstateinordertokeeptrackofalloriginalpropertyvaluesforanentityaswellasnewvalues.Maintaininglargeamountsofstate
canaffectapplicationperformancebecauseiteitherrequiresserverresourcesormustbeincludedinthewebpageitself(forexample,inhiddenfields)orinacookie.
YoucanletJane'schangeoverwriteJohn'schange.ThenexttimesomeonebrowsestheEnglishdepartment,they'llsee8/8/2013andtherestored$350,000.00value.ThisiscalledaClient
WinsorLastinWinsscenario.(Allvaluesfromtheclienttakeprecedenceoverwhat'sinthedatastore.)As
notedintheintroductiontothissection,ifyoudon'tdoanycodingforconcurrencyhandling,thiswillhappenautomatically.
YoucanpreventJane'schangefrombeingupdatedinthedatabase.Typically,youwoulddisplayanerrormessage,showherthecurrentstateofthedata,andallowhertoreapplyherchangesifshestillwantstomakethem.ThisiscalledaStore
Winsscenario.(Thedata-storevaluestakeprecedenceoverthevaluessubmittedbytheclient.)You'llimplementtheStoreWinsscenariointhistutorial.Thismethodensuresthatnochangesareoverwrittenwithoutauserbeingalertedtowhat'shappening.
DetectingConcurrencyConflicts
YoucanresolveconflictsbyhandlingthattheEntityFrameworkthrows.Inordertoknowwhentothrowtheseexceptions,theEntityFrameworkmustbeabletodetectconflicts.Therefore,youmustconfigurethedatabaseandthedatamodelappropriately.Someoptionsforenablingconflictdetection
includethefollowing:
Inthedatabasetable,includeatrackingcolumnthatcanbeusedtodeterminewhenarowhasbeenchanged.YoucanthenconfiguretheEntityFrameworktoincludethatcolumninthe
Whereclause
ofSQL
Updateor
Deletecommands.
Thedatatypeofthetrackingcolumnistypically
.The
isasequentialnumberthat'sincrementedeachtimetherowisupdated.Inan
Updateor
Deletecommand,
the
Whereclauseincludestheoriginalvalueofthetrackingcolumn
(theoriginalrowversion).Iftherowbeingupdatedhasbeenchangedbyanotheruser,thevalueinthe
columnrowversion
isdifferentthantheoriginalvalue,sothe
Updateor
Deletestatement
can'tfindtherowtoupdatebecauseofthe
Whereclause.When
theEntityFrameworkfindsthatnorowshavebeenupdatedbythe
Updateor
Deletecommand
(thatis,whenthenumberofaffectedrowsiszero),itinterpretsthatasaconcurrencyconflict.
ConfiguretheEntityFrameworktoincludetheoriginalvaluesofeverycolumninthetableinthe
Whereclause
of
Updateand
Deletecommands.
Asinthefirstoption,ifanythingintherowhaschangedsincetherowwasfirstread,the
Whereclause
won'treturnarowtoupdate,whichtheEntityFrameworkinterpretsasaconcurrencyconflict.Fordatabasetablesthathavemanycolumns,thisapproachcanresultinverylarge
Whereclauses,
andcanrequirethatyoumaintainlargeamountsofstate.Asnotedearlier,maintaininglargeamountsofstatecanaffectapplicationperformance.Thereforethisapproachisgenerallynotrecommended,anditisn'tthemethodusedinthistutorial.
Ifyoudowanttoimplementthisapproachtoconcurrency,youhavetomarkallnon-primary-keypropertiesintheentityyouwanttotrackconcurrencyforbyaddingthe
tothem.ThatchangeenablestheEntityFrameworktoincludeallcolumnsintheSQL
WHEREclause
of
UPDATEstatements.
Intheremainderofthistutorialyou'lladda
propertytothe
Departmententity,createacontrollerandviews,
andtesttoverifythateverythingworkscorrectly.
AddanOptimisticConcurrencyPropertytotheDepartmentEntity
InModels\Department.cs,addatrackingpropertynamedRowVersion:
publicclassDepartment { publicintDepartmentID{get;set;} [StringLength(50,MinimumLength=3)] publicstringName{get;set;} [DataType(DataType.Currency)] [Column(TypeName="money")] publicdecimalBudget{get;set;} [DataType(DataType.Date)] [DisplayFormat(DataFormatString="{0:yyyy-MM-dd}",ApplyFormatInEditMode=true)] [Display(Name="StartDate")] publicDateTimeStartDate{get;set;} [Display(Name="Administrator")] publicint?InstructorID{get;set;} [Timestamp] publicbyte[]RowVersion{get;set;} publicvirtualInstructorAdministrator{get;set;} publicvirtualICollection<Course>Courses{get;set;} }
The
specifiesthatthiscolumnwillbeincludedinthe
Whereclause
of
Updateand
Deletecommands
senttothedatabase.Theattributeiscalled
previousversionsofSQLServerusedaSQL
typebeforetheSQL
it.The.Nettypefor
abytearray.
IfyouprefertousethefluentAPI,youcanusethe
tospecifythetrackingproperty,asshowninthefollowingexample:
modelBuilder.Entity<Department>()
.Property(p=>p.RowVersion).IsConcurrencyToken();
Byaddingapropertyyouchangedthedatabasemodel,soyouneedtodoanothermigration.InthePackageManagerConsole(PMC),enterthefollowingcommands:
Add-MigrationRowVersion
Update-Database
ModifytheDepartmentController
InControllers\DepartmentController.cs,addausingstatement:
usingSystem.Data.Entity.Infrastructure;
IntheDepartmentController.csfile,changeallfouroccurrencesof"LastName"to"FullName"sothatthedepartment
administratordrop-downlistswillcontainthefullnameoftheinstructorratherthanjustthelastname.
ViewBag.InstructorID=newSelectList(db.Instructors,"InstructorID","FullName");
Replacetheexistingcodeforthe
HttpPost
Editmethod
withthefollowingcode:
[HttpPost]
[ValidateAntiForgeryToken]
publicasyncTask<ActionResult>Edit(
[Bind(Include="DepartmentID,Name,Budget,StartDate,RowVersion,InstructorID")]
Departmentdepartment)
{
try
{
if(ModelState.IsValid)
{
db.Entry(department).State=EntityState.Modified;
awaitdb.SaveChangesAsync();
returnRedirectToAction("Index");
}
}
catch(DbUpdateConcurrencyExceptionex)
{
varentry=ex.Entries.Single();
varclientValues=(Department)entry.Entity;
vardatabaseEntry=entry.GetDatabaseValues();
if(databaseEntry==null)
{
ModelState.AddModelError(string.Empty,
"Unabletosavechanges.Thedepartmentwasdeletedbyanotheruser.");
}
else
{
vardatabaseValues=(Department)databaseEntry.ToObject();
if(databaseValues.Name!=clientValues.Name)
ModelState.AddModelError("Name","Currentvalue:"
+databaseValues.Name);
if(databaseValues.Budget!=clientValues.Budget)
ModelState.AddModelError("Budget","Currentvalue:"
+String.Format("{0:c}",databaseValues.Budget));
if(databaseValues.StartDate!=clientValues.StartDate)
ModelState.AddModelError("StartDate","Currentvalue:"
+String.Format("{0:d}",databaseValues.StartDate));
if(databaseValues.InstructorID!=clientValues.InstructorID)
ModelState.AddModelError("InstructorID","Currentvalue:"
+db.Instructors.Find(databaseValues.InstructorID).FullName);
ModelState.AddModelError(string.Empty,"Therecordyouattemptedtoedit"
+"wasmodifiedbyanotheruserafteryougottheoriginalvalue.The"
+"editoperationwascanceledandthecurrentvaluesinthedatabase"
+"havebeendisplayed.Ifyoustillwanttoeditthisrecord,click"
+"theSavebuttonagain.OtherwiseclicktheBacktoListhyperlink.");
department.RowVersion=databaseValues.RowVersion;
}
}
catch(RetryLimitExceededException/*dex*/)
{
//Logtheerror(uncommentdexvariablenameandaddalineheretowritealog.
ModelState.AddModelError(string.Empty,"Unabletosavechanges.Tryagain,andiftheproblempersistscontactyoursystemadministrator.");
}
ViewBag.InstructorID=newSelectList(db.Instructors,"ID","FullName",department.InstructorID);
returnView(department);
}
Theviewwillstoretheoriginal
RowVersionvalueinahiddenfield.
Whenthemodelbindercreatesthe
departmentinstance,thatobject
willhavetheoriginal
RowVersionpropertyvalueandthenewvalues
fortheotherproperties,asenteredbytheuserontheEditpage.ThenwhentheEntityFrameworkcreatesaSQL
UPDATEcommand,
thatcommandwillincludea
WHEREclausethatlooksforarow
thathastheoriginal
RowVersionvalue.
Ifnorowsareaffectedbythe
UPDATEcommand(norowshavethe
original
RowVersionvalue),theEntityFrameworkthrowsa
exception,DbUpdateConcurrencyException
andthecodeinthe
catchblockgetstheaffected
Departmententity
fromtheexceptionobject.
varentry=ex.Entries.Single();
Thisobjecthasthenewvaluesenteredbytheuserinits
Entityproperty,
andyoucangetthevaluesreadfromthedatabasebycallingthe
GetDatabaseValuesmethod.
varclientValues=(Department)entry.Entity;
vardatabaseEntry=entry.GetDatabaseValues();
The
GetDatabaseValuesmethodreturnsnullifsomeonehasdeleted
therowfromthedatabase;otherwise,youhavetocasttheobjectreturnedtothe
Departmentclass
inordertoaccessthe
Departmentproperties.
if(databaseEntry==null)
{
ModelState.AddModelError(string.Empty,
"Unabletosavechanges.Thedepartmentwasdeletedbyanotheruser.");
}
else
{
vardatabaseValues=(Department)databaseEntry.ToObject();
Next,thecodeaddsacustomerrormessageforeachcolumnthathasdatabasevaluesdifferentfromwhattheuserenteredontheEditpage:
if(databaseValues.Name!=currentValues.Name)
ModelState.AddModelError("Name","Currentvalue:"+databaseValues.Name);
//...
Alongererrormessageexplainswhathappenedandwhattodoaboutit:
ModelState.AddModelError(string.Empty,"Therecordyouattemptedtoedit"
+"wasmodifiedbyanotheruserafteryougottheoriginalvalue.The"
+"editoperationwascanceledandthecurrentvaluesinthedatabase"
+"havebeendisplayed.Ifyoustillwanttoeditthisrecord,click"
+"theSavebuttonagain.OtherwiseclicktheBacktoListhyperlink.");
Finally,thecodesetsthe
RowVersionvalueofthe
Departmentobject
tothenewvalueretrievedfromthedatabase.Thisnew
RowVersionvalue
willbestoredinthehiddenfieldwhentheEditpageisredisplayed,andthenexttimetheuserclicksSave,only
concurrencyerrorsthathappensincetheredisplayoftheEditpagewillbecaught.
InViews\Department\Edit.cshtml,addahiddenfieldtosavethe
RowVersionproperty
value,immediatelyfollowingthehiddenfieldforthe
DepartmentIDproperty:
@modelContosoUniversity.Models.Department
@{
ViewBag.Title="Edit";
}
<h2>Edit</h2>
@using(Html.BeginForm())
{
@Html.AntiForgeryToken()
<divclass="form-horizontal">
<h4>Department</h4>
<hr/>
@Html.ValidationSummary(true)
@Html.HiddenFor(model=>model.DepartmentID)
@Html.HiddenFor(model=>model.RowVersion)
TestingOptimisticConcurrencyHandling
RunthesiteandclickDepartments:RightclicktheEdithyperlinkfortheEnglishdepartmentandselectOpen
innewtab,thenclicktheEdithyperlinkfortheEnglishdepartment.Thetwotabsdisplaythesameinformation.
ChangeafieldinthefirstbrowsertabandclickSave.
ThebrowsershowstheIndexpagewiththechangedvalue.
ChangeafieldinthesecondbrowsertabandclickSave.
ClickSaveinthesecondbrowsertab.Youseeanerrormessage:
ClickSaveagain.Thevalueyouenteredinthesecondbrowsertabissavedalongwiththeoriginalvalueofthedata
youchangedinthefirstbrowser.YouseethesavedvalueswhentheIndexpageappears.
UpdatingtheDeletePage
FortheDeletepage,theEntityFrameworkdetectsconcurrencyconflictscausedbysomeoneelseeditingthedepartmentinasimilarmanner.WhentheHttpGet
Deletemethod
displaystheconfirmationview,theviewincludestheoriginal
RowVersionvalue
inahiddenfield.Thatvalueisthenavailabletothe
HttpPost
Deletemethod
that'scalledwhentheuserconfirmsthedeletion.WhentheEntityFrameworkcreatestheSQL
DELETEcommand,
itincludesa
WHEREclausewiththeoriginal
RowVersionvalue.
Ifthecommandresultsinzerorowsaffected(meaningtherowwaschangedaftertheDeleteconfirmationpagewasdisplayed),aconcurrencyexceptionisthrown,andthe
HttpGet Deletemethodiscalledwithanerrorflagsetto
truein
ordertoredisplaytheconfirmationpagewithanerrormessage.It'salsopossiblethatzerorowswereaffectedbecausetherowwasdeletedbyanotheruser,sointhatcaseadifferenterrormessageisdisplayed.
InDepartmentController.cs,replacethe
HttpGet
Deletemethod
withthefollowingcode:
publicActionResultDelete(int?id,bool?concurrencyError)
{
if(id==null)
{
returnnewHttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Departmentdepartment=db.Departments.Find(id);
if(department==null)
{
returnHttpNotFound();
}
if(concurrencyError.GetValueOrDefault())
{
if(department==null)
{
ViewBag.ConcurrencyErrorMessage="Therecordyouattemptedtodelete"
+"wasdeletedbyanotheruserafteryougottheoriginalvalues."
+"ClicktheBacktoListhyperlink.";
}
else
{
ViewBag.ConcurrencyErrorMessage="Therecordyouattemptedtodelete"
+"wasmodifiedbyanotheruserafteryougottheoriginalvalues."
+"Thedeleteoperationwascanceledandthecurrentvaluesinthe"
+"databasehavebeendisplayed.Ifyoustillwanttodeletethis"
+"record,clicktheDeletebuttonagain.Otherwise"
+"clicktheBacktoListhyperlink.";
}
}
returnView(department);
}
Themethodacceptsanoptionalparameterthatindicateswhetherthepageisbeingredisplayedafteraconcurrencyerror.Ifthisflagis
true,
anerrormessageissenttotheviewusinga
ViewBagproperty.
Replacethecodeinthe
HttpPost
Deletemethod
(named
DeleteConfirmed)withthefollowingcode:
[HttpPost]
[ValidateAntiForgeryToken]
publicActionResultDelete(Departmentdepartment)
{
try
{
db.Entry(department).State=EntityState.Deleted;
db.SaveChanges();
returnRedirectToAction("Index");
}
catch(DbUpdateConcurrencyException)
{
returnRedirectToAction("Delete",new{concurrencyError=true});
}
catch(DataException/*dex*/)
{
//Logtheerror(uncommentdexvariablenameafterDataExceptionandaddalineheretowritealog.
ModelState.AddModelError(string.Empty,"Unabletodelete.Tryagain,andiftheproblempersistscontactyoursystemadministrator.");
returnView(department);
}
}
Inthescaffoldedcodethatyoujustreplaced,thismethodacceptedonlyarecordID:
publicActionResultDeleteConfirmed(intid)
You'vechangedthisparametertoa
Departmententityinstancecreated
bythemodelbinder.Thisgivesyouaccesstothe
RowVersionproperty
valueinadditiontotherecordkey.
publicActionResultDelete(Departmentdepartment)
Youhavealsochangedtheactionmethodnamefrom
DeleteConfirmedto
Delete.
Thescaffoldedcodenamedthe
HttpPost
Deletemethod
DeleteConfirmedto
givethe
HttpPostmethodauniquesignature.(TheCLRrequires
overloadedmethodstohavedifferentmethodparameters.)Nowthatthesignaturesareunique,youcanstickwiththeMVCconventionandusethesamenameforthe
HttpPostand
HttpGetdelete
methods.
Ifaconcurrencyerroriscaught,thecoderedisplaystheDeleteconfirmationpageandprovidesaflagthatindicatesitshoulddisplayaconcurrencyerrormessage.
InViews\Department\Delete.cshtml,replacethescaffoldedcodewiththefollowingcodethataddsanerrormessagefield
andhiddenfieldsfortheDepartmentIDandRowVersionproperties.Thechangesarehighlighted.
@modelContosoUniversity.Models.Department
@{
ViewBag.Title="Delete";
}
<h2>Delete</h2>
<pclass="error">@ViewBag.ConcurrencyErrorMessage</p>
<h3>Areyousureyouwanttodeletethis?</h3>
<div>
<h4>Department</h4>
<hr/>
<dlclass="dl-horizontal">
<dt>
Administrator
</dt>
<dd>
@Html.DisplayFor(model=>model.Administrator.FullName)
</dd>
<dt>
@Html.DisplayNameFor(model=>model.Name)
</dt>
<dd>
@Html.DisplayFor(model=>model.Name)
</dd>
<dt>
@Html.DisplayNameFor(model=>model.Budget)
</dt>
<dd>
@Html.DisplayFor(model=>model.Budget)
</dd>
<dt>
@Html.DisplayNameFor(model=>model.StartDate)
</dt>
<dd>
@Html.DisplayFor(model=>model.StartDate)
</dd>
</dl>
@using(Html.BeginForm()){
@Html.AntiForgeryToken()
@Html.HiddenFor(model=>model.DepartmentID)
@Html.HiddenFor(model=>model.RowVersion)
<divclass="form-actionsno-color">
<inputtype="submit"value="Delete"class="btnbtn-default"/>|
@Html.ActionLink("BacktoList","Index")
</div>
}
</div>
Thiscodeaddsanerrormessagebetweenthe
h2and
h3headings:
<pclass="error">@ViewBag.ConcurrencyErrorMessage</p>
Itreplaces
LastNamewith
FullNamein
the
Administratorfield:
<dt>
Administrator
</dt>
<dd>
@Html.DisplayFor(model=>model.Administrator.FullName)
</dd>
Finally,itaddshiddenfieldsforthe
DepartmentIDand
RowVersionproperties
afterthe
Html.BeginFormstatement:
@Html.HiddenFor(model=>model.DepartmentID)
@Html.HiddenFor(model=>model.RowVersion)
RuntheDepartmentsIndexpage.RightclicktheDeletehyperlinkfortheEnglishdepartmentandselectOpen
innewtab,theninthefirsttabclicktheEdithyperlinkfortheEnglishdepartment.
Inthefirstwindow,changeoneofthevalues,andclickSave:
TheIndexpageconfirmsthechange.
Inthesecondtab,clickDelete.
Youseetheconcurrencyerrormessage,andtheDepartmentvaluesarerefreshedwithwhat'scurrentlyinthedatabase.
IfyouclickDeleteagain,you'reredirectedtotheIndexpage,whichshowsthatthedepartmenthasbeendeleted.
Summary
Thiscompletestheintroductiontohandlingconcurrencyconflicts.Forinformationaboutotherwaystohandlevariousconcurrencyscenarios,seeOptimisticConcurrencyPatternsandWorking
withPropertyValuesonMSDN.Thenexttutorialshowshowtoimplementtable-per-hierarchyinheritanceforthe
Instructorand
Studententities.
LinkstootherEntityFrameworkresourcescanbefoundintheASP.NET
DataAccess-RecommendedResources.
相关文章推荐
- 使用EF6和MVC5实现一个简单的选课系统--使用EF6异步编程模式和存储过程(9/12)
- 使用EF6和MVC5实现一个简单的选课系统--使用EF6实现继承(11/12)
- 使用EF6和MVC5实现一个简单的选课系统--使用EF6实现基本的GRUD功能(2/12)
- 使用EF6和MVC5实现一个简单的选课系统--排序、过滤和分页(3/12)
- 使用EF6和MVC5实现一个简单的选课系统--EF6的弹性链接和命令拦截(4/12)
- 使用EF6和MVC5实现一个简单的选课系统--EF6的代码优先迁移和部署(5/12)
- 使用EF6和MVC5实现一个简单的选课系统--启航(1/12)
- 使用EF6和MVC5实现一个简单的选课系统--使用EF6读取相关数据(7/12)
- 使用EF6和MVC5实现一个简单的选课系统--使用EF6更新相关数据(8/12)
- 使用EF6和MVC5实现一个简单的选课系统--EF6的高级用法(12/12)
- 这是一个秒杀系统,即大量用户抢有限的商品,先到先得 用户并发访问流量非常大,需要分布式的机器集群处理请求 系统实现使用Java
- Python(10)使用python函数实现一个简单的闭包操作
- php使用face++实现一个简单的人脸识别系统
- 使用Node.js + MongoDB实现一个简单的日志分析系统
- 使用python实现一个简单的学生信息管理系统
- 使用Node.js + MongoDB实现一个简单的日志分析系统
- 使用python实现一个简单的学生信息管理系统
- 使用Select I/O模型来实现一个并发处理多个客户端的TCP服务器
- 使用Akka的Actor和Future简单地实现并发处理
- 一个非常简单的缓冲—使用Java5提供的读写锁处理多线程操作