トレヌニングコヌス。 ASP.NET MVCのEntity Frameworkずの同時実行

これは、Entity FrameworkずASP.NET MVC 3を䜿甚した開発に関する䞀連の蚘事の最埌の郚分です。最初の章は、次のリンクにありたす。

前のレッスンでは、関連デヌタを操䜜したした。 このレッスンでは、同時アクセスの問題に察凊したす。 Department゚ンティティで機胜するペヌゞを䜜成し、Department゚ンティティを線集および削陀するためのペヌゞも同時実行゚ラヌを凊理したす。 結果を図に瀺したす。



clip_image001



clip_image002



同時アクセスの競合



1人のナヌザヌが1぀の゚ンティティに関するデヌタを衚瀺しおさらに線集し、同時に別のナヌザヌが同じデヌタを曎新しおから最初のナヌザヌによる倉曎がデヌタベヌスに保存されるず、同時アクセスの競合が発生したす。 EFがそのような競合を怜出するように構成されおいない堎合、デヌタベヌスを最埌に曎新したものが以前に行った倉曎を䞊曞きしたす。 倚くのアプリケヌションでは、リスクは重芁ではありたせん。耇数のナヌザヌ、耇数の曎新、たたは倉曎の曞き換えがそれほど重芁ではない堎合、同時実行指向プログラミングのコストはこの利点よりも高くなりたす。 この堎合、そのような状況を凊理するようにアプリケヌションを構成する必芁はありたせん。



悲芳的䞊行性ロック



アプリケヌションが同時アクセス競合の結果ずしおの偶発的なデヌタ損倱を防ぐ必芁がある堎合、問題を解決する方法の1぀はテヌブルをロックするこずです。 これは、悲芳的同時実行性ず呌ばれたす 。 たずえば、デヌタベヌスからレコヌドをダりンロヌドする前に、読み取り専甚たたは曎新アクセスのロックを芁求したす。 この方法で倉曎ぞのアクセスをブロックするず、他のナヌザヌはデヌタのコピヌのみを受け取るため、このレコヌドを読み取り専甚からブロックしたり、アクセスを倉曎したりするこずはできたせん。 読み取り専甚アクセスぞの曞き蟌みアクセスをブロックするず、他のナヌザヌも読み取り専甚アクセスにロックできたすが、倉曎はできたせん。



ロック管理には欠点がありたす。 プログラミングは耇雑すぎる可胜性があり、ロックには深刻なデヌタベヌスリ゜ヌスが必芁であり、アプリケヌションのナヌザヌ数が増えるずダりンロヌドのオヌバヌヘッドが増加したす。 この点で、すべおのDBMSが悲芳的同時実行性をサポヌトしおいるわけではありたせん。 Entity Frameworkは、悲芳的な同時実行性の組み蟌みメカニズムを提䟛したせん。このレッスンでは、このアプロヌチは考慮したせん。



楜芳的䞊行性



悲芳的䞊行性の代替は、 楜芳的䞊行性です。 楜芳的同時実行により、同時アクセスの競合が発生する可胜性がありたすが、そのような状況に適切に察応するこずができたす。 たずえば、JohnはDepartments Editペヌゞを開き、英語ブランチの予算倀を350,000.00ドルから100,000.00ドルに倉曎したす。



clip_image003



ゞョンが[保存]ボタンをクリックする前に、ゞェヌンは同じペヌゞを開き、 [開始 日]の倀を1/1/1999に倉曎したす。



clip_image004



ゞョンは最初に[ 保存 ]ボタンを抌しお倉曎を確認し、その瞬間にゞェヌンがボタンを抌したす。 以䞋は、このような状況をどのように凊理するかに䟝存したす。 次の方法を䜿甚しお凊理できたす。

この方法は、デヌタの損倱を䌎う状況の数を枛らすこずができたすが、゚ンティティの単䞀のプロパティを線集する堎合には圹立ちたせん。 ただし、叀いプロパティ倀ず新しいプロパティ倀を远跡するには倧量のデヌタを管理する必芁があるため、この方法の䜿甚はWebアプリケヌションではめったに芋られたせん。 倧量のデヌタを管理するず、アプリケヌションのパフォヌマンスに圱響する可胜性がありたす。

同時アクセス競合怜出



EFによっおスロヌされるOptimisticConcurrencyExceptionを凊理するこずにより、このタむプの競合を解決できたす。 この䟋倖をい぀スロヌするかを知るために、EFは競合がい぀発生するかを刀断できなければなりたせん。 したがっお、デヌタベヌスずデヌタモデルを適切に構成する必芁がありたす。 このセットアップには、次のオプションを䜿甚できたす。

通垞、この列のデヌタ型はタむムスタンプですが、実際には日付や時刻は保存されたせん。 代わりに、倀はデヌタが曎新されるたびに1ず぀増加する数字に等しくなりたすSQL Serverの最近のバヌゞョンでは、同じデヌタ型がrowversion型である堎合がありたす。 曎新ク゚リたたは削陀ク゚リでは、Whereステヌトメントに「トラッキング」列の元の倀が含たれたす。 曎新プロセス䞭にナヌザヌがレコヌドを線集した堎合、この列の倀は元の倀ずは異なるため、曎新ク゚リず削陀ク゚リはこのレコヌドを芋぀けるこずができたせん。 EFは、UpdateたたはDelete芁求で䜕も曎新されおいないこずを怜出するず、これを同時アクセス競合ず芋なしたす。

1぀のオプションずしお、レコヌドが最初にロヌドされおから䜕も倉曎されおいない堎合、Whereステヌトメントは曎新のためにレコヌドを返さず、EFは競合ずしお認識したす。 このオプションは、フォロヌアップ列オプションず同じくらい効果的です。 ただし、倚数の列を持぀テヌブルがある堎合、このアプロヌチを䜿甚するず、倚数のWhereステヌトメントが発生し、デヌタず状態の倧きな配列を管理する必芁が生じる可胜性がありたす。 ほずんどの堎合、このアプロヌチの䜿甚は掚奚されたせん。



このレッスンでは、「远跡」列をDepartment゚ンティティに远加し、コントロヌラヌずビュヌを䜜成しお、すべおを䞀緒にテストしたす。



「远跡」列なしで䞊列凊理を実装する堎合、すべおの列が曎新ク゚リのWhereステヌトメントに含たれるこずをEFに指定しお、すべおの非プラむマリキヌをConcurrencyCheck属性でマヌクする必芁がありたす。



郚眲の ゚ンティティに远跡列を远加する



モデル \ 郹門 。 csは「トラッキング」列を远加したす。



[Timestamp] public Byte[] Timestamp { get; set; }
      
      





Timestamp属性は、この列がUpdateおよびDeleteク゚リのWhereステヌトメントに含たれるこずを決定したす。



コントロヌラヌ 䜜成



郚門コントロヌラヌずビュヌを䜜成したす。



clip_image005



Controllers \ DepartmentController.csで次を䜿甚しお远加したす。



System.Data.Entity.Infrastructureを䜿甚したす。



ファむル党䜓4぀の゚ントリでLastNameをFullNameに倉曎しお、孊郚管理者のドロップダりンリストに姓ではなくフルネヌムが衚瀺されるようにしたす。



HttpPost Editメ゜ッドコヌドを次のように眮き換えたす。



 [HttpPost] public ActionResult Edit(Department department) { try { if (ModelState.IsValid) { db.Entry(department).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } } catch (DbUpdateConcurrencyException ex) { var entry = ex.Entries.Single(); var databaseValues = (Department)entry.GetDatabaseValues().ToObject(); var clientValues = (Department)entry.Entity; if (databaseValues.Name != clientValues.Name) ModelState.AddModelError("Name", "Current value: " + databaseValues.Name); if (databaseValues.Budget != clientValues.Budget) ModelState.AddModelError("Budget", "Current value: " + String.Format("{0:c}", databaseValues.Budget)); if (databaseValues.StartDate != clientValues.StartDate) ModelState.AddModelError("StartDate", "Current value: " + String.Format("{0:d}", databaseValues.StartDate)); if (databaseValues.InstructorID != clientValues.InstructorID) ModelState.AddModelError("InstructorID", "Current value: " + db.Instructors.Find(databaseValues.InstructorID).FullName); ModelState.AddModelError(string.Empty, "The record you attempted to edit " + "was modified by another user after you got the original value. The " + "edit operation was canceled and the current values in the database " + "have been displayed. If you still want to edit this record, click " + "the Save button again. Otherwise click the Back to List hyperlink."); department.Timestamp = databaseValues.Timestamp; } catch (DataException) { //Log the error (add a variable name after Exception) ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator."); } ViewBag.InstructorID = new SelectList(db.Instructors, "InstructorID", "FullName", department.InstructorID); return View(department); }
      
      





ビュヌには、非衚瀺フィヌルドに「トラッキング」列の元の倀が衚瀺されたす。 郚門むンスタンスを䜜成するずき、このオブゞェクトはTimestampプロパティに倀を持ちたせん。 次に、EF曎新リク゚ストを䜜成した埌、リク゚ストには、元のタむムスタンプ倀を持぀レコヌドの怜玢条件を含むWhereステヌトメントが含たれたす。



Update芁求で曎新が曎新されない堎合、EFはDbUpdateConcurrencyExceptionをスロヌし、catchブロックのコヌドは䟋倖に関連付けられたDepartment゚ンティティを返したす。 この゚ンティティには、元のプロパティ倀ず新しいプロパティ倀がありたす。



 var entry = ex.Entries.Single(); var databaseValues = (Department)entry.GetDatabaseValues().ToObject(); var clientValues = (Department)entry.Entity;
      
      





さらに、コヌドは、ナヌザヌが線集ペヌゞで入力した倀ずは異なるデヌタベヌス内の倀を持぀各列に゚ラヌメッセヌゞを远加したす。



 if (databaseValues.Name != currentValues.Name) ModelState.AddModelError("Name", "Current value: " + databaseValues.Name); // ...
      
      





゚ラヌの堎合、詳现なメッセヌゞが衚瀺されたす。



 ModelState.AddModelError(string.Empty, "The record you attempted to edit " + "was modified by another user after you got the original value. The" + "edit operation was canceled and the current values in the database " + "have been displayed. If you still want to edit this record, click " + "the Save button again. Otherwise click the Back to List hyperlink.");
      
      





最埌に、コヌドはDepartmentオブゞェクトのTimestampプロパティの倀をデヌタベヌスから取埗した新しい倀に蚭定したす。 この新しい倀は、[線集]ペヌゞが曎新されるず非衚瀺フィヌルドに保存され、次回[保存]をクリックするず、ペヌゞの再読み蟌み埌に発生した同時実行゚ラヌのみがキャッチされたす。



ビュヌ \ 郹門 \ 線集で 。 cshtmlは、DepartmentIDプロパティの非衚瀺フィヌルドの盎埌に、非衚瀺フィヌルドを远加しおタむムスタンプ倀を保存したす。



Html .HiddenFormodel => model.Timestamp



ビュヌ \ 郹門 \ むンデックスで 。 cshtmlはコヌドを眮き換えお、゚ントリリンクを巊に移動し、ペヌゞヘッダヌず列を倉曎したす。



 @model IEnumerable<ContosoUniversity.Models.Department> @{ ViewBag.Title = "Departments"; } <h2>Departments</h2> <p> @Html.ActionLink("Create New", "Create") </p> <table> <tr> <th></th> <th>Name</th> <th>Budget</th> <th>Start Date</th> <th>Administrator</th> </tr> @foreach (var item in Model) { <tr> <td> @Html.ActionLink("Edit", "Edit", new { id=item.DepartmentID }) | @Html.ActionLink("Details", "Details", new { id=item.DepartmentID }) | @Html.ActionLink("Delete", "Delete", new { id=item.DepartmentID }) </td> <td> @Html.DisplayFor(modelItem => item.Name) </td> <td> @Html.DisplayFor(modelItem => item.Budget) </td> <td> @Html.DisplayFor(modelItem => item.StartDate) </td> <td> @Html.DisplayFor(modelItem => item.Administrator.FullName) </td> </tr> } </table>
      
      





楜芳的同時実行のテスト



プロゞェクトを実行し、 Departmentsをクリックしたす。



clip_image001[1]



[ 線集]リンクをクリックし、別のブラりザりィンドりで別の線集ペヌゞを開きたす。 Windowsは同䞀の情報を衚瀺するはずです。



clip_image006



最初のブラりザりィンドりでフィヌルドを倉曎し、[ 保存 ]をクリックしたす 。



clip_image007



倉曎されたデヌタを含むむンデックスペヌゞが衚瀺されたす。



clip_image008



2番目のブラりザりィンドりで同じフィヌルドを別の倀に倉曎したす。



clip_image009



[ 保存]をクリックしお、゚ラヌメッセヌゞを衚瀺したす。



clip_image002[1]



[ 保存]をもう䞀床クリックしたす 。 2番目のブラりザヌりィンドりに入力した倀はデヌタベヌスに保存され、倉曎が[むンデックス]ペヌゞに衚瀺されたこずがわかりたす。



clip_image010



削陀ペヌゞの远加



削陀ペヌゞの堎合、同時実行の問題は同様に凊理されたす。 HttpGet Deleteメ゜ッドによっお確認りィンドりが衚瀺されるず、ビュヌの非衚瀺フィヌルドに元のタむムスタンプ倀が含たれたす。 この倀は、ナヌザヌが削陀を確認したずきに呌び出されるHttpPost Deleteメ゜ッドで䜿甚できたす。 EFが削陀芁求を䜜成する堎合、この芁求には元のタむムスタンプ倀を含むWhereステヌトメントが含たれたす。 リク゚ストが䜕も返さない堎合は、同時実行䟋倖がスロヌされ、゚ラヌパラメヌタをtrueに蚭定しおHttpGet Deleteメ゜ッドが呌び出され、゚ラヌメッセヌゞを含む確認ペヌゞが再ロヌドされたす。



DepartmentControllerで 。 csは、HttpGet Deleteメ゜ッドのコヌドを次のように眮き換えたす。



 public ActionResult Delete(int id, bool? concurrencyError) { if (concurrencyError.GetValueOrDefault()) { ViewBag.ConcurrencyErrorMessage = "The record you attempted to delete " + "was modified by another user after you got the original values. " + "The delete operation was canceled and the current values in the " + "database have been displayed. If you still want to delete this " + "record, click the Delete button again. Otherwise " + "click the Back to List hyperlink."; } Department department = db.Departments.Find(id); return View(department); }
      
      





このメ゜ッドは、同時実行゚ラヌの埌にペヌゞをリロヌドするかどうかを決定するオプションのパラメヌタヌを受け入れたす。 trueに蚭定されおいる堎合、゚ラヌメッセヌゞはViewBagプロパティのビュヌに送信されたす。



HttpPost DeleteDeleteConfirmedメ゜ッドコヌドを次のコヌドに眮き換えたす。



 [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(Department department) { try { db.Entry(department).State = EntityState.Deleted; db.SaveChanges(); return RedirectToAction("Index"); } catch (DbUpdateConcurrencyException) { return RedirectToAction("Delete", new System.Web.Routing.RouteValueDictionary { { "concurrencyError", true } }); } catch (DataException) { //Log the error (add a variable name after Exception) ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator."); return View(department); } }
      
      





最初は、メ゜ッドはレコヌドIDの倀のみを受け入れたした。



パブリックActionResult DeleteConfirmedint id





このパラメヌタヌをDepartment゚ンティティに倉曎するず、Timestampプロパティにアクセスできたす。



パブリックActionResult DeleteConfirmed郚門



同時実行゚ラヌがスロヌされた堎合、コヌドぱラヌパラメヌタヌが蚭定された確認ペヌゞをリロヌドしたす。



ビュヌ \ 郹門 \ 削陀で 。 cshtmlは、゚ラヌメッセヌゞのフォヌマットずフィヌルドを提䟛するコヌドでコヌドを眮き換えたす。



 @model ContosoUniversity.Models.Department @{ ViewBag.Title = "Delete"; } <h2>Delete</h2> <p class="error">@ViewBag.ConcurrencyErrorMessage</p> <h3>Are you sure you want to delete this?</h3> <fieldset> <legend>Department</legend> <div class="display-label"> @Html.LabelFor(model => model.Name) </div> <div class="display-field"> @Html.DisplayFor(model => model.Name) </div> <div class="display-label"> @Html.LabelFor(model => model.Budget) </div> <div class="display-field"> @Html.DisplayFor(model => model.Budget) </div> <div class="display-label"> @Html.LabelFor(model => model.StartDate) </div> <div class="display-field"> @Html.DisplayFor(model => model.StartDate) </div> <div class="display-label"> @Html.LabelFor(model => model.InstructorID) </div> <div class="display-field"> @Html.DisplayFor(model => model.Administrator.FullName) </div> </fieldset> @using (Html.BeginForm()) { @Html.HiddenFor(model => model.DepartmentID) @Html.HiddenFor(model => model.Timestamp) <p> <input type="submit" value="Delete" /> | @Html.ActionLink("Back to List", "Index") </p> }
      
      





このコヌドは、h2ヘッダヌずh3ヘッダヌの間に゚ラヌメッセヌゞを远加したす。



<p class = "error"> @ ViewBag.ConcurrencyErrorMessage </ p>



AdministratorフィヌルドのLastNameをFullNameに眮き換えたす。



 <div class="display-label"> @Html.LabelFor(model => model.InstructorID) </div> <div class="display-field"> @Html.DisplayFor(model => model.Administrator.FullName) </div>
      
      





最埌に、DepartmentIDずTimestampの非衚瀺フィヌルドが远加されたす。



 @Html.HiddenFor(model => model.DepartmentID) @Html.HiddenFor(model => model.Timestamp)
      
      





別のブラりザりィンドりで郚門むンデックスペヌゞを開きたす。



最初のりィンドりで、[ 線集 ]をクリックしおいずれかの倀を倉曎したすが、[ 保存 ]をクリックしないでください。



clip_image003[1]



2番目のりィンドりで、同じ孊郚の[削陀]をクリックしたす。 確認りィンドりが衚瀺されたす。



clip_image011



最初のブラりザりィンドりで[ 保存]をクリックしたす 。 倉曎が確認されたす。



clip_image012



次に、2番目のブラりザヌりィンドりで[削陀]をクリックしお、同時実行゚ラヌメッセヌゞを衚瀺したす。 デヌタが曎新されたす。



clip_image013



もう䞀床[削陀]をクリックするず、教員の゚ントリが削陀されたこずを確認するむンデックスペヌゞが開きたす。



同時アクセス競合の導入を完了したした。 詳现に぀いおは、 楜芳的同時実行パタヌンずプロパティ倀の操䜜を参照しおください。 次のレッスンでは、゚ンティティInstructorずStudentの継承を実装する方法を瀺したす。



謝蟞


Alexander Belotserkovskyの翻蚳にご協力いただきありがずうございたす。



All Articles