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();
....
Related
An odd thing happened after an Add
context.Activities.Add(activity);
context.SaveChanges();
immediately after trying to use the navigation fields
CommandId = activity.CommandId;
if (CommandId != 0)
{
CommandName = activity.Command.CommandName;
}
ActivityCategoryId = activity.ActivityCategoryId;
if (ActivityCategoryId != 0)
{
ActivityCategoryName = activity.ActivityCategory.Name;
}
"Command" is null and "ActivityCategory" is not. Both were created as part of the "Update Model from Database" and both have public null argument constructors. Both work on normal queries say to populate a table. The proxies are present for this. However, after an Add, the proxy for command is not present.
generated code definitions
public virtual ActivityCategory ActivityCategory { get; set; }
public virtual Command Command { get; set; }
If I explicitly "Include", works fine.
public static IList<DAL.Activity> GetActivitiesByCommandI(DAL.MSMTEntities context, int id)
{
IList<DAL.Activity> list = context.Activities
.Include(a => a.Command)
.FilterActivitiesByCommandId(id)
.ToList();
return list;
}
public static IQueryable<DAL.Activity> FilterActivitiesByCommandId(this IQueryable<DAL.Activity> query, int id)
{
IQueryable<DAL.Activity> result = query
.Where(act => act.CommandId == id);
return result;
}
Not sure what to look for next. Will drill down into the context to looks for clues. Appreciate any guidance.
Sorry, this turned out to be a stray entitydatasource in the markup. Why it hacked up on the spatial type, I have not a clue. I downloaded the ef6 from nuget until I can exercise the demon.
Thanks
I'm pretty sure this isn't a duplicate question (or I don't know how to ask it properly) so here goes. I have an ASP.NET MVC project using EF 6.1. In it I have an entity called Member. The Member has a property called Races that can contain one or more Race entities. The Races property on Member:
public virtual ICollection<Race> Races { get; set; }
The Race entity:
public class Race : ILookupListItem {
public int Id { get; set; }
public string Description { get; set; }
public bool Active { get; set; }
[JsonIgnore]
public virtual ICollection<Member> Members { get; set; }
}
The Race entity is what I have always called a lookup.
My issue arises when I try to save the entity after updates have occurred on the client. My SaveMember controller method is below along with my business logic method for actually saving the entities. (To avoid any confusion, InjectFrom is the ValueInjecter method and the JSONResponseError and JSONResponseSuccess classes are our custom wrappers around a JsonResult.)
public JsonResult SaveMemberInfo(MemberInfoModel model) {
try {
// retrieve member
var member = new Member();
member.InjectFrom(model);
var races = new List<Race>();
races.InjectFromList(model.Races);
member.Races = races;
// save member
var savedMember = _members.UpdateMember(member, out errId);
var savedModel = GetMemberChartViewModel(savedMember);
// notify user of success
return new JsonResponseSuccess(savedModel);
}
catch (Exception ex) {
return new JsonResponseError(message);
}
}
The _members.UpdateMember method is below.
public Member UpdateMember(Member contract, out int? errId) {
try {
using (_db = new ApplicationContext(_currentUserID)) {
// updates entity state in the context using the entity's State field from Julie Lerman's example on MSDN.
_db.FixState();
_db.SaveChanges();
errId = null;
return contract;
}
}
catch (Exception ex)
{
// log error to db and return id
errId = LogError(ex, "MemberLogic.UpdateMember");
return null;
}
}
When I get the data contract back from the UpdateMember method, the Races property has all the races saved to the memeber before the update, plus any that I just saved (so it's adding but not doing a diff to determine which items were already there and which were removed). In addition the Races table will now have duplicate entries for the "new" races. My assumption is that this isn't EF's default behavior but I've never worked with a dropdown list that populated a list of items, usually it's just a single property (like Gender or Country). Is there something obvious I'm missing?
Thanks!
EDIT: It just occurred to me that the EntityConfiguration for the Member and Race intersection table would be useful, added below. Obviously, this is only the part I thought to be relevant to the question.
HasMany(t => t.Races).WithMany(t => t.Members).Map(m =>
{
m.ToTable("MemberRaces", "dbo");
m.MapLeftKey("MemberId");
m.MapRightKey("RaceId");
});
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.
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();
I have been tearing my hair out over this for days and before I go completely bald it's time to ask all the people smarter than me how to do this.
I am using Entity Framework 4 with the Code First CTP 5 and MVC 3.
The exception message right now is "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."
First up here is the controller the edit form is posted to:
public ActionResult Save(ClientEntity postedClient)
{
try
{
if (ModelState.IsValid)
{
Base.clientInterface.Save(postedClient);
return RedirectToAction("Index");
}
}
catch (Exception)
{
throw;
}
SetupManageData(null, postedClient);
return View("Manage");
}
The Save method on the client interface is this:
public void Save(ClientEntity theClient)
{
SetContext();
if (theClient.clientId == 0)
this.pContext.Clients.Add(theClient);
else
{
ClientEntity existingClient = GetSingle(theClient.clientId); // Get the existing entity from the data store.
// PseudoCode: Merge existingClient and theClient - Can this be done without using ObjectStateManager?
// PseudoCode: Attach merged entity to context so that SaveChanges will update it in the database - is this correct?
}
this.pContext.SaveChanges();
}
private void SetContext()
{
if (this.pContext == null)
this.pContext = new PersistanceContext();
}
Persistance context is the DBContext and looks like this:
public class PersistanceContext : DbContext
{
public DbSet<ClientEntity> Clients { get; set; }
}
What is the lifestyle of clientInterface? is it a singleton or something that keeps it alive across multiple requests?
My guess is that it holds a live instance of a database context that was used to fetch the entity in the GET request and when the POST tries to (re)add a client entity to the context the old one is still there and they conflict.
Try destroying the object that is behind the clientInterface with every request. Maybe use a DI container that supports per-webrequest lifestyles so you don't have to worry about it.
I hope my guess was right and this is of help.
This should work.
if (theClient.clientId == 0)
{
this.pContext.Clients.Add(theClient);
}
else
{
ClientEntity existingClient = this.pContext.Clients.Single(o => o.ClientId == theClient.ClientId);
// map properties
existingClient.Name = theClient.name;
// ....
}
this.pContext.SaveChanges();
[Edit]
It's easier (IMHO) to split the creation & editing of objects into 2 seperate views and to avoid the mapping of the properties I use TryUpdateModel.
[HttpPost]
public ViewResult Edit(int clientID, FormCollection collection)
{
var client = pContext.Clients.SingleOrDefault(o => o.ID == clientID);
if(!TryUpdateModel(client, collection))
{
ViewBag.UpdateError = "Update Failure";
}
else
{
db.SubmitChanges();
}
return View("Details", client);
}
[HttpPost]
public ViewResult Create(FormCollection collection)
{
var client = new Client();
if(!TryUpdateModel(client, collection))
{
ViewBag.UpdateError = "Create Failure";
}
else
{
db.Clients.Add(client);
db.SubmitChanges();
}
return View("Details", client);
}