Using EntityFramework v6, I am putting together a prototype to demonstrate concurrency checking in a Web Api as well as in a desktop application.
Entity:
public static class IRowVersionExtensions
{
public static string RowVersionAsString(this IRowVersion ivr)
{
return Convert.ToBase64String(ivr.RowVersion);
}
public static void SetRowVersion(this IRowVersion ivr, string rowVersion)
{
ivr.RowVersion = Convert.FromBase64String(rowVersion);
}
}
public interface IRowVersion
{
byte[] RowVersion { get; set; }
}
public class Department : IRowVersion
{
[Key]
public int Id { get; set; }
[Required, MaxLength(255)]
public string Name { get; set; }
public string Description { get; set; }
[Timestamp]
[ConcurrencyCheck]
public byte[] RowVersion { get; set; }
}
DbContext:
public class CompDbContext : DbContextEx
{
public CompDbContext()
: base("Company")
{
this.Configuration.LazyLoadingEnabled = false;
}
public DbSet<Department> Departments { get; set; }
}
The desktop application (console app) has the following code, and throws a DbConcurrencyException as expected: http://pastebin.com/i6yAmVGc
Now, the API controller - when I open the page in two windows and edit one (and save) then try to edit/save the other, it does not throw an exception:
Api Controller Update Action:
[HttpPatch, Route("")]
public Department UpdateDepartment(Department changed)
{
var original = dbContext.Departments.Find(changed.Id);
if (original == null)
this.NotFound();
if (Convert.ToBase64String(changed.RowVersion) != Convert.ToBase64String(original.RowVersion))
Console.WriteLine("Should error.");
original.RowVersion = changed.RowVersion;
original.Name = changed.Name;
original.Description = changed.Description;
dbContext.SaveChanges();
return original;
}
Api Call:
DepartmentVM.prototype.onSave = function (entity) {
var method = entity.id() ? 'PATCH' : 'PUT';
$.ajax({
url: '/api/departments',
method: method,
data: ko.toJSON(entity),
contentType: 'application/json',
dataType: 'JSON'
})
.done(function (data) {
alert('Saved');
entity.rowVersion(data.rowVersion);
entity.id(data.id);
})
.error(function (data) {
alert('Unable to save changes to department.');
});
};
When I break on the line in the controller action:
if (Convert.ToBase64String(changed.RowVersion) != Convert.ToBase64String(original.RowVersion))
On the first save, the changed.RowVersion == original.RowVersion (perfect) and it saves (as expected). On the second page's save, the changed.RowVersion != original.RowVersion (perfect) but it still saves, no exception (not as expected).
Can some one help me understand why this works just fine in a desktop application but does not work in a Web API?
It's not working because EF uses the "original" value of RowVersion to perform the concurrency check. In your example, the original value (as far as the DbContext is concerned) is the value from the database, because it was loaded from the database using .Find().
Say, for example, the RowVersion of the changed entity is 1, and the current RowVersion in the database is 2...
// changed's RowVersion is 1
var original = dbContext.Departments.Find(changed.Id);
// original's RowVersion is 2
if (original == null)
this.NotFound();
if (Convert.ToBase64String(changed.RowVersion) != Convert.ToBase64String(original.RowVersion))
Console.WriteLine("Should error."); // 2 != 1, so prints this line
original.RowVersion = changed.RowVersion;
// original's "current" RowVersion is now 1
// ... but its "original" RowVersion is still 2!
original.Name = changed.Name;
original.Description = changed.Description;
dbContext.SaveChanges();
// UPDATE DEPT SET ... WHERE Id = ... AND RowVersion = 2
// (works, therefore no concurrency exception)
To make this work, you can just add the incoming entity to the context...
[HttpPatch, Route("")]
public Department UpdateDepartment(Department changed)
{
dbContext.Entry(changed).State = EntityState.Modified;
dbContext.SaveChanges();
// you'll get an exception if RowVersion has changed
return changed;
}
If you only want to change Name and Description, you can selectively mark those properties as changed and the rest are not updated...
[HttpPatch, Route("")]
public Department UpdateDepartment(Department changed)
{
dbContext.Entry(changed).State = EntityState.Unchanged;
dbContext.Entry(changed).Property(d => d.Name).IsModified = true;
dbContext.Entry(changed).Property(d => d.Description).IsModified = true;
dbContext.SaveChanges();
// you'll get an exception if RowVersion has changed
return changed;
}
The reason the console app worked was a bit lucky. There's a race condition in which if the Find() in t1 executes after the SaveChanges() in t2 (or vice versa), you'd run into the same situation.
Related
I have a model class like so (generated from EF):
public partial class Point
{
public int Id { get; set; }
public int First { get; set; }
public int Second { get; set; }
public int Total { get; set; }
}
and a Post method (which is called from Angular) in my controller (ApiController) like so:
[ResponseType(typeof(Point))]
public IHttpActionResult PostPoint(Point points)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
points.Total = points.First + points.Second;
db.ScoreBoard.Add(points);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = points.Id }, points);
}
This will return the whole points object and my POST call will have a Response with all the properties of that object.
Now what I want to do is to return only the Total property and so I tried to changed the return of the PostPoint to :
return CreatedAtRoute("DefaultApi", new { id = points.Id }, points.Total);
Doing this however will return an empty Response to the POST call from Angular.
The Angular post-method looks like this:
this.post = function (Point) {
var request = $http({
method: "post",
url: "/api/PointsAPI",
data: JSON.stringify(Point)
});
return request;
}
How would I go about only returning the Total property of the pointsobject?
It will be empty because you are returning a primitive data type. Your Total property is just a number so when angular parses the response it expects an object and all it sees is "2" (if your Total was 2). So, wrap it in an object and angular will be able to parse it.
return CreatedAtRoute("DefaultApi", new { id = points.Id }, new { points.Total });
This will return { "Total" : 2 } in your body. Now angular will have an object with a Total property.
I have an unusual problem. One field in the database (FK_ItemType) will not update itself. I went into the vs debug, and checked that int ItemType was assigned, that it found a record in the database, that it assigned that record to item.FK_ItemType, and that that record stayed attached all the way to db.SaveChanges() (I used a breakpoint to check the value of item.FK_ItemType at db.Entry(item).State = EntityState.Modified;
To be extra sure, immediately after I commented out my redirect and pulled the record from the database and checked what the value was, returning that instead of the redirect. It returns the correct value.
However, when I look in the database, or go to my listing page, the newly updated ItemType is not there (still NULL in the database). Even more puzzling, everything works perfectly fine when adding a record.
Here are the relevant sections of my controller and model
[HttpPost]
public ActionResult Edit(Item item, int Company = 0, int Service = 0, int ItemType = 0)
{
if (Request.Form["doDelete"] == "true")
return Delete(item);
//Get, assign foreign keys
Company c = db.Companies.Find(Company);
Service s = db.Services.Find(Service);
ItemType i = db.ItemTypes.Find(ItemType);
if (c != null)
item.FK_Company = c;
if (s != null)
item.FK_Service = s;
if (i != null)
item.FK_ItemType = i;
//Force revalidate
ModelState.Clear();
//TryUpdateModel(item);
TryValidateModel(item);
if (ModelState.IsValid)
{
if (item.ItemID == 0) //add
db.Items.Add(item);
else
{
db.Entry(item).State = EntityState.Modified;
}
db.SaveChanges();
Item i2 = db.Items.Find(item.ItemID);
Response.Write(i2.FK_ItemType.Name);
return null;
// Return to the listing page, and show the user the filters they were looking at before editing.
return Redirect("~/" + Request.RequestContext.RouteData.Values["Controller"].ToString() + "/Index/" + Request.Form["ref"]);
}
// There was a validation error
if (item.ItemID != 0)
return Update(item);
else
return Add(item);
}
Model
public class Item
{
public int ItemID { get; set; }
...
[Required]
[Display(Name="Company")]
public virtual Company FK_Company { get; set; }
[Required]
[Display(Name="Service")]
public virtual Service FK_Service { get; set; }
[Display(Name="Item Type")]
public virtual ItemType FK_ItemType { get; set; }
}
Despite the name FK_ItemType this property is not a FK (foreign key) property. It is a navigation property and relationships represented by navigation properties are not updated when you set the state to Modified. (The same applies to FK_Company and FK_Service.)
You can either introduduce "real" foreign key properties...
[ForeignKey("FK_ItemType")]
public int RealFK_ItemType { get; set; }
public virtual ItemType FK_ItemType { get; set; }
// please give those properties better names..., like "ItemTypeID" and "ItemType"
...and then use the FK RealFK_ItemType directly to change relationships.
Or you must load the original Item from the database first (including related entities) to change the relationships by setting the navigation properties. The code for the UPDATE would be something like this:
var itemIdDb = db.Items
.Include(i => i.Company)
.Include(i => i.Service)
.Include(i => i.ItemType)
.Single(i => i.ItemID == item.ItemID);
Company c = db.Companies.Find(Company);
Service s = db.Services.Find(Service);
ItemType i = db.ItemTypes.Find(ItemType);
if (c != null)
itemInDb.FK_Company = c;
if (s != null)
itemInDb.FK_Service = s;
if (i != null)
itemInDb.FK_ItemType = i;
db.Entry(itemInDb).CurrentValues.SetValues(item);
db.SaveChanges();
Ok, this is probably a concept that i've got wrong, but anyways...
(MVC3) I have an entity with a list property on it. My CRUD views work sending a JSon representation to the controller, via an Ajax post. Everything is working great, except that when i'm posting an update of that entity, the list property is not being updated at all. All the simple properties of the entity are updated, but (as I imagine) the update tree is not including the List property. How can I make the EF aware of those changes on the list?
Here's some of the code so far:
[HttpPost]
public JsonResult Edit(Lote lote)
{
//Given the IDs present in lote.Documentos, load the List of Documentos
if (lote.Documentos != null)
{
List<Documento> ldoc = new List<Documento>();
foreach (var d in lote.Documentos)
{
ldoc.Add(db.Documentos.Find(d.IDDocumento));
}
lote.Documentos.Clear();
foreach (var d in ldoc)
{
lote.Documentos.Add(d);
}
}
//Now, clear all the previous errors
foreach (var modelValue in ModelState.Values)
{
modelValue.Errors.Clear();
}
//And re-validate the model
ValidateModel(lote);
if (ModelState.IsValid)
{
if (lote.IDLote > 0)
{
//Updating
db.Entry(lote).State = EntityState.Modified;
}
else
{
//Inserting
db.Lotes.Add(lote);
}
db.SaveChanges();
CustomMessages.Sucesso(TempData, "Informações salvas com sucesso.", 10000);
return Json(new { Success = 1, IDProprietario = lote.IDLote, ex = "" });
}
else
{
return Json(new { Success = 0, ex = "Falha na rotina de armazenamento das informações"});
}
And those are the classes themselves:
public class Lote
{
[Key]
public int IDLote { get; set; }
(... lots of properties ...)
[Display(Name = "Documentos")]
public List<Documento> Documentos { get; set; }
}
public class Documento
{
//---=== ATRIBUTOS ===---
[Key]
public int IDDocumento { get; set; }
[Required]
[MaxLength(60)]
public string Nome { get; set; }
public List<Lote> Lotes { get; set; }
}
As this is a Many-to-Many relationship, i also got this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove
<System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention>();
modelBuilder.Entity<Lote>()
.HasMany(t => t.Documentos)
.WithMany(t => t.Lotes)
.Map(m =>
{
m.ToTable("LoteDocumento");
m.MapLeftKey("IDLote");
m.MapRightKey("IDDocumento");
});
(... and some other stuff)
Any help on this?
Try changing this line:
ldoc.Add(db.Documentos.Find(d.IDDocumento));
to
ldoc.Add(db.Documentos.Include("Lotes").FirstOrDefault(x => x.IDDocumento == d.IDDocumento));
You need to make sure that the relatioship/objects that you are changing are in fact attached to your current DB context. Otherwise entity framework wont be able to track changes made to them.
This link explains it in terms of object context, but I think the same rules apply to DBcontext.
If that doesn't work, let me know cause I am really trying to get better at understanding the way EF works.
Okay, found some more link that will help:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key
Entity Framework and Connection Pooling
When you execute Include("Lotes") that adds your lotes related to your Documentos to the DBContext. Entity framework is now tracking these objects. Further down in your code you are re-adding them to the context with this line db.Entry(lote).State = EntityState.Modified; That's my guess anyway.
based on the links above i would try and re-write what you have like this (not compiled):
[HttpPost]
public JsonResult Edit(Lote lote)
{
//get old Lote from DB
var oldLote = db.Lotes.include("Documentos").FirstOrDefault(x => x.IDLote == lote.IDLote);
//update
if(oldLote != null)
{
//refresh any other properties that you may have changed on it.
db.Entry(oldLote).CurrentValues.SetValues(lote);
//not sure if you will even need this section any more but then you can...
oldLote.Documentos.Clear();
foreach (var d in lote.Documentos)
{
oldLote.Documentos.Add(db.Documentos.include("Lotes").FirstOrDefault(x => x.IDDocumento == d.IDDocumento));
}
}
else //add
{
//not sure if this will work
foreach (var d in lote.Documentos)
{
db.Entry(d).State = EntityState.Modified;
}
db.Lotes.Add(lote);
}
//then just save changes. EF is already tracking all your objects and changes.
db.SaveChanges();
....
For web application in development(ASP.Net MVC), I'm using the telerik grid. The grid is bound to an IQueryable of my list, because it's a big table, and I want that telerik apply it's filter on the list, and then executes this result, not dowloading 10'000 rows(with the joined tables), and then with the filter, use only rows.
I'm using(and I really need it for this page, it's one of the key feature) the filter/order of the grid.
One of the main column(determining the kind of the data) is an enum.
The problem is that I get a "Specified type member is not supported in linq to entities" as soon as I'm trying to filter/sort it.
I've to bind it on the enum(and not the mapped int) because if I use the id, filters/order by will be on an int, and I can't expect that the user knows the id of the foreign table.
I just cannot implement myself again all grids parameter(located in url)(I assume, it's either I do everything, or nothing) and filter it correctly, order it correctly).
Do you have an idea of workaround?
I don't know how your Entity Model looks like but I'll suppose that you've something like this Model:
public partial class Project
{
public int Id { get; set; }
public string Name { get; set; }
public int Status { get; set; }
}
and the Status property represents your enum value then you've this enum:
public enum ProjectStatuses
{
Current = 1,
Started = 2,
Stopped = 3,
Finished = 4,
}
Then just create new ViewModel like this :
public class ProjectDetails
{
public int Id { get; set; }
public string Name { get; set; }
public int Status { get; set; }
public ProjectStatuses StatusValue { get { return (ProjectStatuses) Status; } }
// This property to display in telerik ClientTemplate
public string StatusName { get { return Enum.GetName(typeof (ProjectStatuses), Status ); } }
}
And because I love Extension Methods I'll add this one :
public static class ModelListExtensions
{
public static IQueryable<ProjectDetails> ToViewModelDetails(this IQueryable<Project> modelList)
{
return modelList.Select(m => new ProjectDetails
{
Id = m.Id,
Name = m.Name,
Status = m.Status,
};
}
}
Update :
Here is the Controller
public ActionResult Index()
{
int total;
var viewModel = getGridList(out total);
ViewBag.Total = total;
return View(viewModel);
}
//this Action to get ajax pages
[GridAction(EnableCustomBinding = true)]
public ActionResult ReGetIndex(GridCommand command, int roleId)
{
int total;
var list = getGridList(out total, roleId, command);
return View(new GridModel {Data = list, Total = total});
}
private IEnumerable<ProjectDetails> getGridList(out int total, GridCommand command = null)
{
command = command ?? new GridCommand {Page = 1};
foreach (var descriptor in command.SortDescriptors)
{
if (descriptor.Member == "StatusValue")
descriptor.Member = "Status";
}
foreach (FilterDescriptor descriptor in command.FilterDescriptors)
{
if (descriptor.Member == "StatusValue")
descriptor.Member = "Status";
}
var list = modelService.AllAsQuery()
.ToViewModelDetails() // To convert it to our ViewModel if we have one
.Where(command.FilterDescriptors);
total = list.Count();
return (IEnumerable<ProjectDetails>) list.Sort(command.SortDescriptors)
.Page(command.Page - 1, command.PageSize)
.GroupBy(command.GroupDescriptors).ToIList();
}
And this is the View
#model IEnumerable<ProjectDetails>
#{
Html.Telerik()
.Grid(Model)
.Name("ProjectsGrid")
.Sortable()
.Filterable()
.EnableCustomBinding(true)
.DataBinding(dataBinding => dataBinding
.Ajax()
.Select("ReGetIndex", "Projects"))
.Pageable(page => page.Style(GridPagerStyles.PageSizeDropDown | GridPagerStyles.NextPreviousAndNumeric).Total(ViewBag.Total))
.Columns(column =>
{
column.Bound(m => m.Id).Hidden(true);
column.Bound(m => m.Name);
column.Bound(m => m.StatusValue).ClientTemplate("<#= StatusName #>");
})
.Render();
}
Update :
If you want to enforce at least one sort order you could use something like this:
if (!command.SortDescriptors.Any())
{
command.SortDescriptors.Add(new SortDescriptor {Member = "YourDefaultProperty"});
}
You don't really have choice (or few annoying choices)
Wether you use a class instead of enum (but if you used an enum, that's because it was better).
Or you "pseudo-sort" your enum, and use the mapped int.
public enum TT
{
Brown = 0,
Green = 1
}
Of course, you'll have to check the actual datas (mapped int) in your DB and update them to conform to the new order (can't change enum order without impact). And you'll have to do that everytime you want to insert a value between existing enum values.
Or you wait for next EF / linq / c# version, which should have enum support in linq2entities
I'm using jqGrid to display some data on a page. Within the controller action, we're using an anonymous object to represent the data that the jqGrid needs. My question is, is there a way we can create a strongly typed object to represent the jqGrid data that we are sending with Json()?
Main reason for this is so that we can do unit testing with the objects that are being sent to it.
Thanks!
EDIT:
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult GridData(FormCollection form, string alias, string location, string state)
{
int pageSize = Convert.ToInt32(form["rows"]);
int pageIndex = Convert.ToInt32(form["page"]) - 1;
var deviceList = this._device.GetList(CreateFilter(location,alias,state),this._securityCache.GetSecurityContext(),pageSize,pageIndex);
int totalResults = deviceList.TotalRecords;
int totalPages = (int)Math.Ceiling((float)totalResults / (float)pageSize);
var jsonData = new {
total = totalPages,
page = pageIndex + 1,
records = totalResults,
rows = (from device in deviceList.Data
select new {i = device.Alias,cell = new string[]{device.Alias,device.Location,device.RatePlan,device.State,device.DateCreated.ToString()}}).ToArray()
};
return Json(jsonData);
This above here works, but we can't unit test the data that is being passed into the Json() method.
var newJsonData = new JsonJQGridReturnData();
newJsonData.total = totalPages;
newJsonData.page = pageIndex + 1;
newJsonData.records = totalResults;
List<JsonJQGridRow> list = new List<JsonJQGridRow>();
foreach (var device in deviceList.Data)
{
list.Add(new JsonJQGridRow(device.Alias, new string[] { device.Alias, device.Location, device.RatePlan, device.State, device.DateCreated.ToString() }));
}
newJsonData.rows = list.ToArray();
_cookieHelper.SaveCookie("DeviceListIndex", this._securityCache.GetSecurityContext().UserID.ToString(), COOKIE_PAGE_SIZE_KEY, pageSize.ToString());
return Json(newJsonData);
}
Here is my poor attempt at trying to wrap these into strongly typed objects. Unfortunately, running this gives me a "u is undefined" in the jqGrid file. I suspect that this is because the json being passed in is not correctly formatted. Here are the classes....
[DataContract]
public class JsonJQGridReturnData
{
[DataMember]
public int total { get; set; }
[DataMember]
public int page { get; set; }
[DataMember]
public int records { get; set; }
[DataMember]
public JsonJQGridRow[] rows { get; set; }
}
[DataContract]
public class JsonJQGridRow
{
public JsonJQGridRow(string i, string[] columns)
{
this.i = i;
this.cells = columns;
}
[DataMember]
public string i { get; set; }
[DataMember]
public string[] cells { get; set; }
}
If I understand your question you can use Generics to do this:
Model:
// represents one row in the JQGrid
class Customer
{
public string firstname { get; set; }
public string lastname { get; set; }
}
JQGrid class:
class JQGridData<TModel>
{
// add in whatever other properties you want for JQGrid
public int responseTime {get; set; };
public List<TModel> rows = new List<TModel>();
}
Controller Action :
public JsonResult GridData(int page)
{
var gridData = new JQGridData<Customer>();
// Populate your data here, this is just an example:
gridData.rows.Add(new Customer()
{
firstname = "fred", lastname = "pharkas"
});
// return the result
return Json(gridData, JsonRequestBehavior.AllowGet);
}
Result:
{
responseTime: 0
rows: [
{
firstname: "fred"
lastname: "pharkas"
}
]
}
Is that what you were asking?
David,
Here's the kinda thing i use in an app i'm working on at the moment for this type of thing. I know it doesn't provide a strongly typed object as such, but the 'list' could be a part of the model that is then sent ToArray() at the end of the piece.
public JsonResult GridData(int id)
{
// get our messages based on id
var bookingmessagesList = _repository.Find(x => x.ID == id);
var list = new ArrayList();
foreach (var bookingmessage in bookingmessagesList) //populate data containers with read data
{
list.Add(new
{
bookingmessage.ClassRowVersionDate,
bookingmessage.ID,
bookingmessage.BookingID,
bookingmessage.AssignedFrom,
bookingmessage.AssignedTo,
bookingmessage.AssignedDate,
bookingmessage.CompletedDate,
bookingmessage.MessageType,
bookingmessage.Notes
});
}
int totalOjectCount = list.Count;
return Json(new { dataitems = list.ToArray(), totalItems = totalOjectCount });
}
hope it gives you some ideas.. Will be interested to see the suggestions made.
Here's a quick take on a strongly-typed JQGridResult.
public class JQGridResult<T> : JsonResult where T : class
{
public T Model
{
get { return (T)this.Data; }
set { this.Data = value; }
}
}
Used as...
return new JQGridResult<JsonModel> {
Model = new GridModel { ... initialize model here ... }
});
where GridModel is basically a container class holding the strongly typed properties for the grid.
I feel really silly. I had a misspelling in the GridRow that was causing jqGrid to blow up. After I fixed that, I was able to get the jqGrid to work with my strongly typed object...
Now in my unit tests, I can just do...
var result = controllerToTest.GridData(form, null, null, null) as JsonResult;
var data = result.Data as JsonJQGridReturnData;
and now I can access the fields :D