This example illustrates how to implement optimistic concurrency when you bind the GridView extension to a data source using the Entity Framework and the "Code First" development approach. See the following topic for more information: Bind Grid View to Data via Entity Framework (Code First).
Note that Entity Framework includes the corresponding functionality out-of-the-box if you define the Timestamp field in your data model:
C#[Timestamp]
public Byte[] RowVersion { get; set; }
Once you define the Timestamp
field, entity framework automatically performs concurrency checks when saving data to the database. In case of concurrency conflict, the DbUpdateConcurrencyException is thrown.
In stateless environments like ASP.NET MVC, you need some additional logic over the default Entity Framework's logic to keep the current RowVersion
value for the specific object between postbacks. To pass RowVersion-related data to the client, you can use the approach illustrated in the following topic: Passing Values Between Server and Client Sides.
C#settings.CustomJSProperties = (s, e) => {
MVCxGridView gridView = (MVCxGridView)s;
var dictionary = new System.Collections.Generic.Dictionary<object, string>(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/);
for (int i = 0; i < gridView.SettingsPager.PageSize; i++) {
var visibleIndex = i + gridView.VisibleStartIndex;
if (visibleIndex >= gridView.VisibleRowCount)
break;
object[] rowValues = (object[])gridView.GetRowValues(visibleIndex, gridView.KeyFieldName, "RowVersion");
dictionary[rowValues[0].ToString(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/)] = Convert.ToBase64String((byte[])rowValues[1]);
}
e.Properties["cpRowVersions"] = new System.Web.Script.Serialization.JavaScriptSerializer(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/).Serialize(dictionary);
if (ViewData["EditError"] != null)
e.Properties["cpEditError"] = ViewData["EditError"];
};
After that, pass this data back to the corresponding Update or Delete controller's action method on the server side. Refer to the following topic for more information: Passing Values to a Controller Action through Callbacks.
JavaScriptfunction GridView_BeginCallback(s, e) {
e.customArgs['RowVersions'] = s.cpRowVersions;
}
On the server side, deserialize this data and pass the correct value to the RowVersion
field of the currently updated or deleted object.
C#public ActionResult GridViewPartialUpdate(Customer customer) {
var model = db.Customers;
customer.RowVersion = CalculateOldRowVersion(customer.Id);
if (ModelState.IsValid) {
try {
db.Entry(customer).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/);
}
catch (Exception e) {
ViewData["EditError"] = e.Message;
}
}
else
ViewData["EditError"] = "Please, correct all errors.";
return PartialView("GridViewPartial", db.Customers.ToList(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/));
}
private byte[] CalculateOldRowVersion(int id) {
JavaScriptSerializer serializer = new JavaScriptSerializer(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/);
string rowVersions = Request["RowVersions"];
Dictionary<object, string> dictionary = (Dictionary<object, string>)serializer.Deserialize(rowVersions, typeof(Dictionary<object, string>));
char[] rowVersion = dictionary[id.ToString(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/)].ToCharArray(https://github.com/DevExpress-Examples/asp-net-mvc-grid-optimistic-concurrency-for-update-delete-operations/tree/24.2.1%2B/);
return Convert.FromBase64CharArray(rowVersion, 0, rowVersion.Length);
}
Files to Review
- HomeController.cs (VB: HomeController.vb)
- Customer.cs (VB: Customer.vb)
- GridViewPartial.cshtml
- Index.cshtml
Documentation
- Bind Grid View to Data via Entity Framework (Code First)
- Passing Values Between Server and Client Sides
- Passing Values to a Controller Action through Callbacks
- Timestamp
Does this example address your development requirements/objectives?
(you will be redirected to DevExpress.com to submit your response)
Example Code
C#using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Script.Serialization;
using GridViewOptimisticConcurrencyMvc.Models;
namespace GridViewOptimisticConcurrencyMvc.Controllers {
public class HomeController : Controller {
private CustomerDbContext db = new CustomerDbContext();
public ActionResult Index() {
return View();
}
public ActionResult GridViewPartial() {
return PartialView(db.Customers.ToList());
}
public ActionResult GridViewPartialAddNew(Customer customer) {
var model = db.Customers;
if (ModelState.IsValid) {
try {
db.Entry(customer).State = System.Data.Entity.EntityState.Added;
db.SaveChanges();
}
catch (Exception e) {
ViewData["EditError"] = e.Message;
}
}
else
ViewData["EditError"] = "Please, correct all errors.";
return PartialView("GridViewPartial", db.Customers.ToList());
}
public ActionResult GridViewPartialUpdate(Customer customer) {
var model = db.Customers;
customer.RowVersion = CalculateOldRowVersion(customer.Id);
if (ModelState.IsValid) {
try {
db.Entry(customer).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
}
catch (Exception e) {
ViewData["EditError"] = e.Message;
}
}
else
ViewData["EditError"] = "Please, correct all errors.";
return PartialView("GridViewPartial", db.Customers.ToList());
}
public ActionResult GridViewPartialDelete(Customer customer) {
var model = db.Customers;
customer.RowVersion = CalculateOldRowVersion(customer.Id);
if (ModelState.IsValid) {
try {
db.Entry(customer).State = System.Data.Entity.EntityState.Deleted;
db.SaveChanges();
}
catch (Exception e) {
ViewData["EditError"] = e.Message;
}
}
else
ViewData["EditError"] = "Please, correct all errors.";
return PartialView("GridViewPartial", db.Customers.ToList());
}
private byte[] CalculateOldRowVersion(int id) {
JavaScriptSerializer serializer = new JavaScriptSerializer();
string rowVersions = Request["RowVersions"];
Dictionary<object, string> dictionary = (Dictionary<object, string>)serializer.Deserialize(rowVersions, typeof(Dictionary<object, string>));
char[] rowVersion = dictionary[id.ToString()].ToCharArray();
return Convert.FromBase64CharArray(rowVersion, 0, rowVersion.Length);
}
}
}
C#using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
namespace GridViewOptimisticConcurrencyMvc.Models {
public class Customer {
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Email { get; set; }
[Timestamp]
public Byte[] RowVersion { get; set; }
}
public class CustomerDbContext : DbContext {
public CustomerDbContext()
: base("CustomerDbContext") {
Database.SetInitializer(new CustomerDbContextInitializer());
}
public DbSet<Customer> Customers { get; set; }
}
public class CustomerDbContextInitializer : DropCreateDatabaseIfModelChanges<CustomerDbContext> {
protected override void Seed(CustomerDbContext context) {
IList<Customer> defaultCustomers = new List<Customer>();
defaultCustomers.Add(new Customer() { FirstName = "David", LastName = "Adler", Age = 25, Email = "David.Adler@somewhere.com" });
defaultCustomers.Add(new Customer() { FirstName = "Michael", LastName = "Alcamo", Age = 38, Email = "Michael.Alcamo@somewhere.com" });
defaultCustomers.Add(new Customer() { FirstName = "Amy", LastName = "Altmann", Age = 27, Email = "Amy.Altmann@somewhere.com" });
foreach (Customer std in defaultCustomers)
context.Customers.Add(std);
base.Seed(context);
}
}
}
Razor@model System.Collections.IEnumerable
@Html.DevExpress().GridView(settings => {
settings.Name = "GridView";
settings.KeyFieldName = "Id";
settings.CallbackRouteValues = new { Controller = "Home", Action = "GridViewPartial" };
settings.SettingsEditing.AddNewRowRouteValues = new { Controller = "Home", Action = "GridViewPartialAddNew" };
settings.SettingsEditing.UpdateRowRouteValues = new { Controller = "Home", Action = "GridViewPartialUpdate" };
settings.SettingsEditing.DeleteRowRouteValues = new { Controller = "Home", Action = "GridViewPartialDelete" };
settings.CommandColumn.Visible = true;
settings.CommandColumn.ShowEditButton = true;
settings.CommandColumn.ShowNewButton = true;
settings.CommandColumn.ShowDeleteButton = true;
settings.Columns.Add(column => {
column.FieldName = "Id";
column.ReadOnly = true;
column.EditFormSettings.Visible = DefaultBoolean.False;
});
settings.Columns.Add("FirstName");
settings.Columns.Add("LastName");
settings.Columns.Add("Age");
settings.Columns.Add("Email");
settings.CustomJSProperties = (s, e) => {
MVCxGridView gridView = (MVCxGridView)s;
var dictionary = new System.Collections.Generic.Dictionary<object, string>();
for (int i = 0; i < gridView.SettingsPager.PageSize; i++) {
var visibleIndex = i + gridView.VisibleStartIndex;
if (visibleIndex >= gridView.VisibleRowCount)
break;
object[] rowValues = (object[])gridView.GetRowValues(visibleIndex, gridView.KeyFieldName, "RowVersion");
dictionary[rowValues[0].ToString()] = Convert.ToBase64String((byte[])rowValues[1]);
}
e.Properties["cpRowVersions"] = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(dictionary);
if (ViewData["EditError"] != null)
e.Properties["cpEditError"] = ViewData["EditError"];
};
settings.ClientSideEvents.BeginCallback = "GridView_BeginCallback";
settings.ClientSideEvents.EndCallback = "GridView_EndCallback";
}).SetEditErrorText((string)ViewData["EditError"]).Bind(Model).GetHtml()
Razor<script type="text/javascript">
function GridView_BeginCallback(s, e) {
e.customArgs['RowVersions'] = s.cpRowVersions;
}
function GridView_EndCallback(s, e) {
if (s.cpEditError) {
alert('Error: ' + s.cpEditError);
delete s.cpEditError;
}
}
</script>
@Html.Action("GridViewPartial")