Before you have learned how to update data, in this section you will learn how to handle conflicts when multiple users update the same entity at the same time.
Modify those pages related to the department entity so that they can handle concurrency errors. The following are the index and delete pages, and the error message when there is a concurrency violation.
Concurrency Conflicts
Concurrency conflicts occur when one user edits the data of an entity, and then another user updates the data for the same entity before the previous user writes the changes to the database. If you do not enable conflict detection, the last update to the database overwrites the changes that other users made to the database. In most applications, this risk is acceptable: if only a small number of users, or very few updates, or overwritten data is not important, implementing concurrency conflicts may not be worth the candle. In this case, you do not need to configure the application to handle concurrency conflicts.
pessimistic concurrency (locked)
If your application needs to prevent accidental data loss due to concurrency, you can use database locks, known as pessimistic concurrency. For example, when you read a record from a database, you can lock it to read-only or update the state. If you lock a record to an updated state, other users will not be able to lock the record again, whether it is a read or update operation. If you lock a record as read-only, other people can lock it as read-only, but not update the operation.
Management locks also have drawbacks, which can lead to more complex programming, require a large amount of database management resources, and may cause performance problems when the number of users increases. Based on the above, not all database management systems support pessimistic concurrency. The Entity framework does not provide built-in support and does not discuss how to implement it in this section.
optimistic concurrency
One of the alternatives to pessimistic concurrency is optimistic concurrency. Optimistic concurrency means that concurrency conflicts are allowed, and then appropriate responses are taken. For example, John opens the Departments edit page and modifies the budget of the Chinese department from $350,000.00 to $0.00.
Before John clicked Save, a named Jane also opened the page and modified the start date to 2014/09/01
John first clicked on the Save,index page to display the modified data, and then Jane clicked Save, and what happens next depends on how you handle the concurrency conflict, and you can work with your app by using the following methods:
- You can track the properties that the user has modified and update only the corresponding columns in the database. In the above example, the data is not lost because two users have updated two different properties respectively. Next, if someone looks at the Chinese department, they will see all the changes that John and Jane have made--2014/09/01 the start date and the $0.00 budget.
This kind of update can reduce conflicts, but if you modify the same property of the same entity at the same time, it can result in data loss and whether the entity Framework uses this method depends on how you implement the update operation. In a Web application, however, this is usually less practical because you need to maintain a large number of States to track all the original properties and modified values of the entity. Maintaining a large number of States can affect the performance of your application because it requires a large amount of server resources or that these States must be included in the Web page itself or in a cookie.
- You can let Jane's changes overwrite John's changes, and then someone will see the 2014/09/01 start date and $350,000.00 's budget, which is referred to as the client first when they view the 中文版 department WINS) or a backward-valid (last-in Wins) policy. (The value of the client overwrites the saved data.) As mentioned above, if you do not take any action to handle concurrency conflicts, the program defaults to using that method.
- You can also prevent Jane from updating the database, typically you should display an error message for the current state of the data, and if she still wants to update the data, you should allow Jane to do these operations, which are known as the store-first (store Wins) policy. This is then used in this section to ensure that changes made by other users are not allowed to be overwritten until the user is prompted for what happens next.
Detecting concurrency Conflicts
You can resolve conflicts by handling Optimisticconcurrencyexception exceptions thrown by the Entity Framework. To know when these exceptions will be thrown, the Entity framework must be able to detect conflicts. Therefore, you must configure the database and the data model appropriately, and here are the methods that you can enable for conflict detection:
- In a database table, a trace column is included to determine when the column was modified. Next, configure the Entity Framework to include the column in the WHERE clause of the SQL Update or Delete command.
The data type of the tracking column is usually the value of rowversion,rowversion, which is a sequential number that is incremented each time the row is updated. In the update or Delete command, the WHERE clause will contain the original value of the tracking column, and if one of the rows being updated has been changed by another user, the value of the rowversion column will be inconsistent with the original, so the update or The DELETE statement cannot find the row to update because of a WHERE clause. When the Entity Framework discovers that an update or delete command does not update any rows, it is identified as a concurrency violation.
- Configure the Entity Framework to include the original values for each column in the database table in the WHERE clause of the update or Delete command.
Just like the first way, if the data row is read and modified for the time being, the WHERE clause does not return the row to be updated and the Entity framework determines it as a concurrency violation. For tables with multiple columns in the database, this approach can produce a large where clause and require you to maintain a large number of States. As you've previously warned, maintaining a large number of States can affect application performance. Therefore, this method is generally deprecated and will not be used in the example.
If you do want to use this method to handle concurrency, you must mark all non-primary key attributes of an entity by adding the Concurrencycheck property. This allows the Entity Framework to include all the columns in the WHERE clause of the UPDATE statement.
Next you will add a rowversion tracking property in the Department entity and create a controller and a view, and finally validate them.
to add optimistic concurrency properties to a department entity
Open Models\department.cs, add a tracking property named Rowcersion
public class department{Public int DepartmentID {get; set;} [Stringlength (minimumlength = 3)] public string Name {get; set;} [DataType (datatype.currency)] [Column (TypeName = "money")] Public decimal Budget {get; set;} [DataType (datatype.date)] [DisplayFormat (dataformatstring = "{0:yyyy-mm-dd}", Applyformatineditmode = True)] [Display (Name = "Start Date")] Public DateTime StartDate {get; set;} [Display (Name = "Administrator")] public int? Instructorid {get; set;} 1537774334 public byte[] RowVersion {get; set;} Public virtual instructor Administrator {get; set;} Public virtual icollection<course> Courses {get; set;}}
The timestamp property specifies that the column will be included in the WHERE clause of the UPDATE or delete command sent to the database. This property is referred to as timestamp because the SQL timestamp data type was used by the previous version of SQL Server and then replaced with SQL RowVersion. RowVersion's. NET type is a byte array.
If you prefer to use the fluent API, you can use the Isconcurrencytoken method to specify the trace properties as follows:
Modelbuilder.entity<department> () . Property (P = p.rowversion). Isconcurrencytoken ();
You have changed the database model by adding properties, so you need to do a migration again. Open Package Manager Console (PMC) and enter the following command:
Add-migration RowVersion Update-database
Modifying the Department controller
Open DepartmentController.cs and add a namespace:
Using System.Data.Entity.Infrastructure;
Open DepartmentController.cs, change all occurrences of LastName to FullName so department administrator the following list contains instructor full name instead of last Name
Viewbag.instructorid = new SelectList (db. Instructors, "Instructorid", "FullName");
To modify the HttpPost Edit method:
[HttpPost] [Validateantiforgerytoken]public Async task<actionresult> Edit ([Bind (Include = "DepartmentID, Name, Budget, Star Tdate, RowVersion, Instructorid ")] Department Department) {try {if (modelstate.isvalid) { Db. Entry (department). state = entitystate.modified; Await DB. Savechangesasync (); Return redirecttoaction ("Index"); }} catch (Dbupdateconcurrencyexception ex) {var entry = ex. Entries.single (); var clientvalues = (Department) entry. Entity; var databaseentry = entry. Getdatabasevalues (); if (databaseentry = = null) {Modelstate.addmodelerror (string. Empty, "Unable to save changes. The department was deleted by another user. "); else {var databasevalues = (Department) databaseentry.toobject (); 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 Valu E: "+ String.Format (" {0:d} ", databasevalues.startdate)); if (Databasevalues.instructorid! = Clientvalues.instructorid) modelstate.addmodelerror ("InstructorID", "Cur Rent value: "+ db. Instructors.find (Databasevalues.instructorid). FullName); Modelstate.addmodelerror (String. Empty, "The record you attempted to edit" + "is modified by another user after you got the original value . The "+" edit operation is canceled and the current values in the database "+" has been D Isplayed. If you still WANT to edit this record, click "+" the Save button again. Otherwise Click the back to List hyperlink. "); Department. RowVersion = databasevalues.rowversion; }} catch (Retrylimitexceededexception/* dex */) {//log the error (uncomment DEX variable name and add a line here to write a log. 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, "ID", "FullName", department. Instructorid); Return View (department);}
The view stores the original rowversion value in a hidden field, and when the model binder creates the department instance, the object will have the new values of the original RowVersion property values and other properties, such as the data entered by the user on the edit page, and the entity The framework generates a SQL UPDATE command that contains a WHERE clause that finds a row with a rowversion value.
If the update operation does not update any rows, the Entity Framework throws an dbupdateconcurrencyexception exception, and the code in the catch block gets the affected department entity from the exception object.
var entry = ex. Entries.single ();
You can also call the Getdatabasevalues method to read the value from the database in the object's Entity property, which has a new value entered by the user.
var clientvalues = (Department) entry. Entity;
var databaseentry = entry. Getdatabasevalues ();
If someone deletes the row from the database, the Getdatabasevalue method returns NULL, otherwise you must convert the returned object to the Department class to access the properties in department.
if (databaseentry = = null) { modelstate.addmodelerror (string. Empty, "Unable to save changes. The department was deleted by another user. "); else{ var databasevalues = (Department) databaseentry.toobject ();
Next, if the user enters data in the edit page that is inconsistent with the data in the database, the above code adds a custom error message to the columns for which the data is inconsistent:
if (databasevalues.name! = currentvalues.name) modelstate.addmodelerror ("Name", "Current value:" + Databasevalues.name); // ...
A longer error message explains what happened to the user and how to resolve it:
Modelstate.addmodelerror (String. Empty, "The record you attempted to edit" + "is modified by another user after you got the original value. The " +" edit operation is canceled and the current values in the database " +" has been displayed. If you still want-to-edit this record, click " +" the Save button again. Otherwise Click the back to List hyperlink. ");
Finally, the code sets the rowversion value of the Department object to the new value retrieved from the database, and when the edit page is re-rendered, the new value rowversion is stored in the hidden field, and the next time the user clicks Save, Captures concurrency errors that occur only when the edit page is re-displayed.
Open views\department\edit.cshtml, add a hidden field after the DepartmentID property to hold the RowVersion property value.
@model contosouniversity.models.department@{ viewbag.title = "Edit";}
test optimistic concurrency processingRun the project, click the Departments tab
Open two 中文版 Department edit page
Change the budget in the first edit page to 0, click Save
The index page shows the modified data
Modify the start Date in the second edit page
Click Save to see the error message
Click Save again to overwrite the data modified on the first edit page
Update Delete PageFor the delete page, the Entity framework detects concurrency conflicts in a manner similar to the above editing department. When the HttpGet Delete method displays a confirmation view, the hidden field of the view contains the original rowversion value. When the user confirms the deletion, the value is passed to the method when the HttpPost delete method is called. When the Entity Framework creates the SQL Delete command, the original rowversion value is included in the WHERE clause of the command. If the command does not delete any rows, the program throws a concurrency exception, the HttpGet Delete method is called, and an error flag bit is set to true to re-display the confirmation page and display an error message. The delete command does not delete any rows or it may be because another user has just deleted the row, in which case we should display a different error message.
Open DepartmentController.cs, modify HttpGet Delete method
Public async task<actionresult> Delete (int? ID, bool concurrencyerror) {if (id = = NULL) {return new H Ttpstatuscoderesult (httpstatuscode.badrequest); } Department Department = await db. Departments.findasync (ID); if (department = = null) {if (Concurrencyerror = = True) {return redirecttoaction ("Index"); } return Httpnotfound (); } if (Concurrencyerror.getvalueordefault ()) {if (department = = null) {viewbag.concurrency ErrorMessage = "The record attempted to delete" + "is deleted by another user after you got the Origi NAL values. "+" Click the back to List hyperlink. ";} else {viewbag.concurrencyerrormessage = ' The record you attempted to delete ' + ' was mod Ified by another user after you got the original values. "+" The delete operation is canceled and the current values in the " + "Database has been displayed. If you still want to delete this "+" record, click the Delete button again. Otherwise "+" click the back to List hyperlink. ";}} Return View (department);}
The method takes an optional parameter to indicate whether to re-display the page when a concurrency error occurs, and if this flag bit is true, the error message is passed to the view using the ViewBag property.Modify the HttpPost Delete method (the one with the name deleteconfirmed)
[HttpPost] [Validateantiforgerytoken]public async task<actionresult> Delete (Department Department) { try { Db. Entry (department). state = entitystate.deleted; Await DB. Savechangesasync (); Return redirecttoaction ("Index"); } catch (dbupdateconcurrencyexception) { return redirecttoaction ("Delete", new {Concurrencyerror = True, id= Department. DepartmentID}); } catch (dataexception/* dex */) { //log the error (uncomment DEX variable name after DataException and add a Lin e here to write a log. Modelstate.addmodelerror (String. Empty, "unable to delete. Try again, and if the problem persists contact your system administrator. "); Return View (department);} }
The Delete method automatically generated by the framework receives only one record ID parameterPublic async task<actionresult> deleteconfirmed (int id)
The modified method receives a department entity parameter created by the model binder, which allows access to the RowVersion propertyPublic async task<actionresult> Delete (Department Department)
You have modified the method name from deleteconfirmed to delete, and the framework code names the HttpPost Delete method deleteconfirmed to give it a unique signature. (The CLR requires overloaded methods with different parameters) now signatures are unique, and you can follow MVC conventions by using the same method name for the HttpPost and httpget Delete methods.
If a concurrency error is caught, the code will re-display the delete confirmation page and provide a flag bit to indicate that the concurrency error message will be displayed.
Open views\department\delete.cshtml, add the Error information field and the hidden field for the DepartmentID and RowVersion properties, as shown below
@model contosouniversity.models.department@{viewbag.title = "Delete";} The above code adds an error message between the H2 and H3 headings<p class= "Error" > @ViewBag .concurrencyerrormessage</p>
Modify the LastName of the Administrator field to FullName
<dt> administrator</dt><dd> @Html. displayfor (model = model. Administrator.fullname) </dd>
Add hidden fields to the DepartmentID and RowVersion properties after the Html.BeginForm statement
@Html. hiddenfor (model = model. DepartmentID) @Html. hiddenfor (model = model. RowVersion)
To run the project, click on the Department tab and open an edit page and a delete page for the 中文版 departmentIn the edit page, modify the value of the budget and click Save
The index page shows the modified value
On the delete page, click Delete
You can see the concurrency error message and display the values that have been modified in the database
If you click Delete again, you will be redirected to the index page and the department has been deleted.
Original: Handling Concurrency with the Entity Framework 6 in an ASP 5 application
Welcome reprint, please indicate the source of the article: http://blog.csdn.net/johnsonblog/article/details/39298201
Also everyone a healthy network environment, from you and I start
The END
MVC5 Entity Framework Learning processing concurrency