Khoanglang89
Bạn hãy đăng nhập hoặc đăng ký
Khoanglang89

NHẬN THIẾT KẾ WEBSITE/ SOFTWARE - LÀM ĐỒ ÁN TỐT NGHIỆP, ĐỒ ÁN CHUYÊN MÔN NGÀNH CÔNG NGHỆ THÔNG TIN


You are not connected. Please login or register

Xem chủ đề cũ hơn Xem chủ đề mới hơn Go down  Thông điệp [Trang 1 trong tổng số 1 trang]

Admin

avatar

Admin
Admin
Loading
In earlier tutorials you learned how to update data. This tutorial shows how to handle conflicts when multiple users update the same entity at the same time.
You'll change the web pages that work with the
Code:
Department
entity so that they handle concurrency errors. The following illustrations show the Index and Delete pages, including some messages that are displayed if a concurrency conflict occurs.


Concurrency Conflicts

A concurrency conflict occurs when one user displays an entity's data in order to edit it, and then another user updates the same entity's data before the first user's change is written to the database. If you don't enable the detection of such conflicts, whoever updates the database last overwrites the other user's changes. In many applications, this risk is acceptable: if there are few users, or few updates, or if isn't really critical if some changes are overwritten, the cost of programming for concurrency might outweigh the benefit. In that case, you don't have to configure the application to handle concurrency conflicts.

Pessimistic Concurrency (Locking)

If your application does need to prevent accidental data loss in concurrency scenarios, one way to do that is to use database locks. This is called pessimistic concurrency. For example, before you read a row from a database, you request a lock for read-only or for update access. If you lock a row for update access, no other users are allowed to lock the row either for read-only or update access, because they would get a copy of data that's in the process of being changed. If you lock a row for read-only access, others can also lock it for read-only access but not for update.
Managing locks has disadvantages. It can be complex to program. It requires significant database management resources, and it can cause performance problems as the number of users of an application increases. For these reasons, not all database management systems support pessimistic concurrency. The Entity Framework provides no built-in support for it, and this tutorial doesn't show you how to implement it.

Optimistic Concurrency

The alternative to pessimistic concurrency is optimistic concurrency. Optimistic concurrency means allowing concurrency conflicts to happen, and then reacting appropriately if they do. For example, John runs the Departments Edit page, changes the Budget amount for the English department from $350,000.00 to $0.00.

Before John clicks Save, Jane runs the same page and changes the Start Date field from 9/1/2007 to 8/8/2013.

John clicks Save first and sees his change when the browser returns to the Index page, then Jane clicks Save. What happens next is determined by how you handle concurrency conflicts. Some of the options include the following:

  • You can keep track of which property a user has modified and update only the corresponding columns in the database. In the example scenario, no data would be lost, because different properties were updated by the two users. The next time someone browses the English department, they'll see both John's and Jane's changes — a start date of 8/8/2013 and a budget of Zero dollars.
    This method of updating can reduce the number of conflicts that could result in data loss, but it can't avoid data loss if competing changes are made to the same property of an entity. Whether the Entity Framework works this way depends on how you implement your update code. It's often not practical in a web application, because it can require that you maintain large amounts of state in order to keep track of all original property values for an entity as well as new values. Maintaining large amounts of state can affect application performance because it either requires server resources or must be included in the web page itself (for example, in hidden fields) or in a cookie.
  • You can let Jane's change overwrite John's change. The next time someone browses the English department, they'll see 8/8/2013 and the restored $350,000.00 value. This is called a Client Wins or Last in Wins scenario. (All values from the client take precedence over what's in the data store.) As noted in the introduction to this section, if you don't do any coding for concurrency handling, this will happen automatically.
  • You can prevent Jane's change from being updated in the database. Typically, you would display an error message, show her the current state of the data, and allow her to reapply her changes if she still wants to make them. This is called a Store Wins scenario. (The data-store values take precedence over the values submitted by the client.) You'll implement the Store Wins scenario in this tutorial. This method ensures that no changes are overwritten without a user being alerted to what's happening.

Detecting Concurrency Conflicts

You can resolve conflicts by handling OptimisticConcurrencyException exceptions that the Entity Framework throws. In order to know when to throw these exceptions, the Entity Framework must be able to detect conflicts. Therefore, you must configure the database and the data model appropriately. Some options for enabling conflict detection include the following:

  • In the database table, include a tracking column that can be used to determine when a row has been changed. You can then configure the Entity Framework to include that column in the
    Code:
    Where
    clause of SQL
    Code:
    Update
    or
    Code:
    Delete
    commands.
    The data type of the tracking column is typically rowversion
    Code:
    .
     The rowversion value is a sequential number that's incremented each time the row is updated. In an
    Code:
    Update
    or
    Code:
    Delete
    command, the
    Code:
    Where
    clause includes the original value of the tracking column (the original row version) . If the row being updated has been changed by another user, the value in the
    Code:

    rowversion
    column is different than the original value, so the
    Code:
    Update
    or
    Code:
    Delete
    statement can't find the row to update because of the
    Code:
    Where
    clause. When the Entity Framework finds that no rows have been updated by the
    Code:
    Update
    or
    Code:
    Delete
    command (that is, when the number of affected rows is zero), it interprets that as a concurrency conflict.
  • Configure the Entity Framework to include the original values of every column in the table in the
    Code:
    Where
    clause of
    Code:
    Update
    and
    Code:
    Delete
    commands.
    As in the first option, if anything in the row has changed since the row was first read, the
    Code:
    Where
    clause won't return a row to update, which the Entity Framework interprets as a concurrency conflict. For database tables that have many columns, this approach can result in very large
    Code:
    Where
    clauses, and can require that you maintain large amounts of state. As noted earlier, maintaining large amounts of state can affect application performance. Therefore this approach is generally not recommended, and it isn't the method used in this tutorial.
    If you do want to implement this approach to concurrency, you have to mark all non-primary-key properties in the entity you want to track concurrency for by adding the ConcurrencyCheck attribute to them. That change enables the Entity Framework to include all columns in the SQL
    Code:
    WHERE
    clause of
    Code:
    UPDATE
    statements.

In the remainder of this tutorial you'll add a rowversion tracking property to the
Code:
Department
entity, create a controller and views, and test to verify that everything works correctly.

Add an Optimistic Concurrency  Property to the Department Entity

In Models\Department.cs, add a tracking property named
Code:

RowVersion
:
public class Department
{
public int DepartmentID { get; set; }

[StringLength(50, 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; }

[Timestamp]
public byte[] RowVersion { get; set; }

public virtual Instructor Administrator { get; set; }
public virtual ICollection Courses { get; set; }
}
The Timestamp attribute specifies that this column will be included in the
Code:
Where
clause of
Code:
Update
and
Code:
Delete
commands sent to the database. The attribute is called Timestamp because previous versions of SQL Server used a SQL timestamp data type before the SQL rowversion replaced it. The .Net type for


rowversion is a byte array. 
If you prefer to use the fluent API, you can use the IsConcurrencyToken method to specify the tracking property, as shown in the following example:
modelBuilder.Entity()
.Property(p => p.RowVersion).IsConcurrencyToken();
By adding a property you changed the database model, so you need to do another migration. In the Package Manager Console (PMC), enter the following commands:
Code:
Add-Migration RowVersion
Update-Database

Modify the Department Controller

In Controllers\DepartmentController.cs, add a
Code:
using
statement:
using System.Data.Entity.Infrastructure;
In the DepartmentController.cs file, change all four occurrences of "LastName" to "FullName" so that the department administrator drop-down lists will contain the full name of the instructor rather than just the last name.
ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FullName");
Replace the existing code for the
Code:
HttpPost

Code:
Edit
method with the following code:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task Edit(int? id, byte[] rowVersion)
{
string[] fieldsToBind = new string[] { "Name", "Budget", "StartDate", "InstructorID", "RowVersion" };

if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}

var departmentToUpdate = await db.Departments.FindAsync(id);
if (departmentToUpdate == null)
{
Department deletedDepartment = new Department();
TryUpdateModel(deletedDepartment, fieldsToBind);
ModelState.AddModelError(string.Empty,
"Unable to save changes. The department was deleted by another user.");
ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FullName", deletedDepartment.InstructorID);
return View(deletedDepartment);
}

if (TryUpdateModel(departmentToUpdate, fieldsToBind))
{
try
{
db.Entry(departmentToUpdate).OriginalValues["RowVersion"] = rowVersion;
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 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.");
departmentToUpdate.RowVersion = databaseValues.RowVersion;
}
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FullName", departmentToUpdate.InstructorID);
return View(departmentToUpdate);
}
If the
Code:
FindAsync
method returns null, the department was deleted by another user. The code shown uses the posted form values to create a department entity so that the Edit page can be redisplayed with an error message. As an alternative, you wouldn't have to re-create the department entity if you display only an error message without redisplaying the department fields.
The view stores the original
Code:
RowVersion
value in a hidden field, and the method receives it in the
Code:
rowVersion
parameter. Before you call
Code:
SaveChanges
, you have to put that original
Code:
RowVersion
property value in the
Code:
OriginalValues
collection for the entity. Then when the Entity Framework creates a SQL
Code:
UPDATE
command, that command will include a
Code:
WHERE
clause that looks for a row that has the original
Code:
RowVersion
value.
If no rows are affected by the
Code:
UPDATE
command (no rows have the original
Code:

RowVersion
value),  the Entity Framework throws a
Code:

DbUpdateConcurrencyException
exception, and the code in the
Code:
catch
block gets the affected
Code:
Department
entity from the exception object.
var entry = ex.Entries.Single();
This object has the new values entered by the user in its
Code:
Entity
property, and you can get the values read from the database by calling the
Code:

GetDatabaseValues
method.
var clientValues = (Department)entry.Entity;
var databaseEntry = entry.GetDatabaseValues(); 
The
Code:
GetDatabaseValues
method returns null if someone has deleted the row from the database; otherwise, you have to cast the returned object to the
Code:
Department
class in order to access the
Code:
Department
properties. (Because you already checked for deletion,
Code:
databaseEntry
would be null only if the department was deleted after
Code:
FindAsync
executes and before
Code:
SaveChanges
executes.) 
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, the code adds a custom error message for each column that has database values different from what the user entered on the Edit page:
if (databaseValues.Name != currentValues.Name)
ModelState.AddModelError("Name", "Current value: " + databaseValues.Name);
// ...
A longer error message explains what happened and what to do about it:
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.");
Finally, the code sets the
Code:
RowVersion
value of the
Code:
Department
object to the new value retrieved from the database. This new
Code:

RowVersion
value will be stored in the hidden field when the Edit page is redisplayed, and the next time the user clicks Save, only concurrency errors that happen since the redisplay of the Edit page will be caught.
In Views\Department\Edit.cshtml, add a hidden field to save the
Code:

RowVersion
property value, immediately following the hidden field for the
Code:
DepartmentID
property:
@model ContosoUniversity.Models.Department

@{
ViewBag.Title = "Edit";
}

Edit




@using (Html.BeginForm())
{
@Html.AntiForgeryToken()


Department




@Html.ValidationSummary(true)
@Html.HiddenFor(model => model.DepartmentID)
@Html.HiddenFor(model => model.RowVersion)

Testing Optimistic Concurrency Handling

Run the site and click Departments:

Right click the Edit hyperlink for the English department and select Open in new tab, then click the Edit hyperlink for the English department. The two tabs display the same information.

Change a field in the first browser tab and click Save.

The browser shows the Index page with the changed value.

Change a field  in the second browser tab and click Save.

Click Save in the second browser tab. You see an error message:

Click Save again. The value you entered in the second browser tab is saved along with the original value of the data you changed in the first browser. You see the saved values when the Index page appears.

Updating the Delete Page

For the Delete page, the Entity Framework detects concurrency conflicts caused by someone else editing the department in a similar manner. When the
Code:
HttpGet

Code:
Delete
method displays the confirmation view, the view includes the original
Code:

RowVersion
value in a hidden field. That value is then available to the
Code:
HttpPost

Code:
Delete
method that's called when the user confirms the deletion. When the Entity Framework creates the SQL
Code:
DELETE
command, it includes a
Code:
WHERE
clause with the original
Code:

RowVersion
value. If the command results in zero rows affected (meaning the row was changed after the Delete confirmation page was displayed), a concurrency exception is thrown, and the
Code:
HttpGet Delete
method is called with an error flag set to
Code:
true
in order to redisplay the confirmation page with an error message. It's also possible that zero rows were affected because the row was deleted by another user, so in that case a different error message is displayed.
In DepartmentController.cs, replace the
Code:
HttpGet

Code:
Delete
method with the following code:
public async Task Delete(int? id, bool? concurrencyError)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Department department = await db.Departments.FindAsync(id);
if (department == null)
{
if (concurrencyError.GetValueOrDefault())
{
return RedirectToAction("Index");
}
return HttpNotFound();
}

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.";
}

return View(department);
}
The method accepts an optional parameter that indicates whether the page is being redisplayed after a concurrency error. If this flag is
Code:
true
, an error message is sent to the view using a
Code:
ViewBag
property.
Replace the code in the
Code:
HttpPost

Code:
Delete
method (named
Code:
DeleteConfirmed
) with the following code:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task 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 line 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);
}
}
In the scaffolded code that you just replaced, this method accepted only a record ID:
public async Task DeleteConfirmed(int id)
You've changed this parameter to a
Code:
Department
entity instance created by the model binder. This gives you access to the
Code:

RowVersion
property value in addition to the record key.
public async Task Delete(Department department)
You have also changed the action method name from
Code:
DeleteConfirmed
to
Code:
Delete
. The scaffolded code named the
Code:
HttpPost

Code:
Delete
method
Code:
DeleteConfirmed
to give the
Code:
HttpPost
  method a unique signature. ( The CLR requires overloaded methods to have different method parameters.) Now that the signatures are unique, you can stick with the MVC convention and use the same name for the
Code:
HttpPost
and
Code:
HttpGet
delete methods.
If a concurrency error is caught, the code redisplays the Delete confirmation page and provides a flag that indicates it should display a concurrency error message.
In Views\Department\Delete.cshtml, replace the scaffolded code with the following code that adds an error message field and hidden fields for the DepartmentID and RowVersion properties. The changes are highlighted.
@model ContosoUniversity.Models.Department

@{
ViewBag.Title = "Delete";
}

Delete



@ViewBag.ConcurrencyErrorMessage



Are you sure you want to delete this?



Department






Administrator



@Html.DisplayFor(model => model.Administrator.FullName)



@Html.DisplayNameFor(model => model.Name)



@Html.DisplayFor(model => model.Name)



@Html.DisplayNameFor(model => model.Budget)



@Html.DisplayFor(model => model.Budget)



@Html.DisplayNameFor(model => model.StartDate)



@Html.DisplayFor(model => model.StartDate)



@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.HiddenFor(model => model.DepartmentID)
@Html.HiddenFor(model => model.RowVersion)


|
@Html.ActionLink("Back to List", "Index")

}

This code adds an error message between the
Code:
h2
and
Code:
h3
headings:

@ViewBag.ConcurrencyErrorMessage


It replaces
Code:
LastName
with
Code:
FullName
in the
Code:
Administrator
field:

Administrator


@Html.DisplayFor(model => model.Administrator.FullName)

Finally, it adds hidden fields for the
Code:
DepartmentID
and
Code:

RowVersion
properties after the
Code:
Html.BeginForm
statement:
@Html.HiddenFor(model => model.DepartmentID)
@Html.HiddenFor(model => model.RowVersion)
Run the Departments Index page. Right click the Delete hyperlink for the English department and select Open in new tab, then in the first tab click the Edit hyperlink for the English department.
In the first window, change one of the values, and click Save :

The Index page confirms the change.

In the second tab, click Delete.

 You see the concurrency error message, and the Department values are refreshed with what's currently in the database.

If you click Delete again, you're redirected to the Index page, which shows that the department has been deleted.

Summary

This completes the introduction to handling concurrency conflicts. For information about other ways to handle various concurrency scenarios, see Optimistic Concurrency Patterns and Working with Property Values on MSDN. The next tutorial shows how to implement table-per-hierarchy inheritance for the
Code:
Instructor
and
Code:
Student
entities.
Links to other Entity Framework resources can be found in the ASP.NET Data Access - Recommended Resources


_________________
Có nỗi buồn triền miên, làm trái tim hoá đá
Có những dòng lệ nhỏ, khiến đá hoá thành tim.

-------------------------------------------------------------------------------------------------------
Program Skills:  ASP.Net MVC 3/4; C#; VB.Net/ VB 6.0; Java Applet/Swing; JS/JavaScript; Bootstrap/ AngularJS; HTML/CSS; Turbo C/ Turbo C++; Pascal...
Xem mẫu Phần mêm quản lý ở đây     -           Xem mẫu Bán hàng trực tuyến ở đây
Nguyễn Ích Hoàn
new_life02081989@yahoo.com
nguyenichhoan1989@gmail.com
Xem lý lịch thành viên http://khoanglang89.forumvi.com

Xem chủ đề cũ hơn Xem chủ đề mới hơn Về Đầu Trang  Thông điệp [Trang 1 trong tổng số 1 trang]

Bài viết mới cùng chuyên mục

      Permissions in this forum:
      Bạn không có quyền trả lời bài viết