This is the code that I have in my controller:
[HttpPost]
public ActionResult UpdateArticle(Article article)
{
if (ModelState.IsValid)
{
article.DateAdded =
this.articleRepository.GetSingle(article.ID).DateAdded;
article.DateModified = DateTime.Now;
this.articleRepository.Update(article);
return this.Redirect("~/Admin");
}
else
{
return this.Redirect("~/Admin/UnsuccessfulOperation");
}
}
From the view the data comes updated. I have a generic repository which handles the saving.
Update looks like this:
public virtual void Update(T entity)
{
//this.context.Entry(entity).State = EntityState.Modified;
this.context.SaveChanges();
}
If I uncomment the first line
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple
objects with the same key.
exception is thrown. When commented nothing is saved.
Any help is appreciated.
UPDATE
Ok the problem seems to be that the updated article is not "part" of the context so when I pass it to the update nothing happens. If I get the entity from the repository itself and pass the new values and after that pass this entity everything works as expected. This piece of code actually updates the date in the repository:
var art = this.articleRepository.GetSingle(article.ID);
art.Text = article.Text;
this.articleRepository.Update(art);
What I don't get is that this works too:
var art = this.articleRepository.GetSingle(article.ID);
art.Text = article.Text;
this.articleRepository.Update(article);
UPDATE 2
Thanks to Vitaliy I now know that attaching is the key, but when I try to attach the new entity I get the same ugly exception
An object with the same key already...
UPDATE 3
As I am not allowed to answer my own question in less than 8h I suppose I have to make another update.
Ok, so this is what I did in order to successfully detach the old and attach the new entity:
public virtual void Update(T entity, object id)
{
this.context.Entry(this.GetSingle(id)).State = EntityState.Detached;
this.context.Entry(entity).State = EntityState.Added;
this.context.Entry(entity).State = EntityState.Modified;
this.context.SaveChanges();
}
I will think of a better way to pass the ID, as it is already part of the entity, perhaps with and interface "myInterface" that has the ID property in it and T will be of type "myInterface".
Thanks a lot to Vitaliy.
You are updating article, which is not attached to Context, thus nothing will be saved.
Probably you intention was to change DateModified then you should do it like this:
public ActionResult UpdateArticle(Guid articleID)
{
if (ModelState.IsValid)
{
var article =
this.articleRepository.GetSingle(articleID);
article.DateModified = DateTime.Now;
this.articleRepository.Update(article);
return this.Redirect("~/Admin");
}
else
{
return this.Redirect("~/Admin/UnsuccessfulOperation");
}
}
Related
I want to add entity payment object, containing EXISTING Currency object to EF database:
public Payment()
{
int Id {get;set;}
public int Value {get;set;}
public Currency SelectedCurrency{get;set;}
}
public Currency()
{
int Id {get;set;}
string Name;
}
Suppose that I have existing Currency attached to new entity Payment(). When I add such entity Payment(), the error appears
Violation of PRIMARY KEY constraint 'PK_dbo.Currency'. Cannot insert duplicate key in object 'dbo.MwbeCurrency'. The duplicate key value is (GBP).\r\nThe statement has been terminated."}
How to add higher-level entity with attached existing lower-level entity?
My code for adding entity is:
public virtual TEntity Add(TEntity entity)
{
return DbSet.Add(entity);
}
public void SaveChanges()
{
Context.SaveChanges();
}
I suspect you retrieved Currency with a different instance than the one that retrieved Payment and did something like this :
payment.Currency = retrievedCurrency;
Therefore, the Payment context things that Currency is a new object and tries to persist it. Since it already exists, you are getting a PRIMARY KEY violation.
If you want to persist Payment correctly, add the following lines:
if (payment.Currency != null && payment.Currency.Id != 0)
{
context.Entry(payment.Currency).State = EntityState.Unchanged;
}
although it would probably be cleaner if you retrieved Payment and Currency with the same context, so you can persist them appropriately.
Calling DbSet.Add(entity) adds the entire graph for persistence, which means it will go through all the navigation properties of entity and set each one's state to EntityState.Added.
While the other answer might work, a better approach is to change the way you are adding the objects, and be explicit about what entities you are adding / updating / etc.
To do this, change:
public virtual void Add(TEntity entity)
{
DbSet.Add(entity);
}
To:
public virtual void Add(TEntity entity)
{
context.Entry(entity).State = EntityState.Added;
}
This will add only the supplied entity. If one of your navigation properties objects is also new, you call .Add(entity) on it as well.
If you do need to add the entire graph in other situations, you can add an additional method that works the way your original one does, but has a better name to indicate it's function:
public virtual void AddGraph(TEntity entity)
{
DbSet.Add(entity);
}
Good Luck
Update
Additionally, since it looks like you are using a repository, I prefer to disable auto detect changes by setting context.Configuration.AutoDetectChangesEnabled = false; If you modify a property on an entity that you want persisted, you would need to set the state of the entity to modified like so:
public virtual void Update(TEntity entity)
{
context.Entry(entity).State = EntityState.Modified;
}
I use the Unity of Work and Generic Repository of CodeCamper.
to update an entity, the generic repo has:
public virtual void Update(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State == EntityState.Detached)
{
DbSet.Attach(entity);
}
dbEntityEntry.State = EntityState.Modified;
}
the web api method:
public HttpResponseMessage Put(MyEditModel editModel)
{
var model = editModel.MapToMyEntity();
_myManager.Update(model);
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
The Update method:
public void Update(MyEntity model)
{
Uow.MyEntities.Update(model);
Uow.Commit();
}
In the Unityof Work:
IRepository<MyEntity> MyEntities { get; }
When updating an entity I get the following error:
Additional information: Attaching an entity of type 'X' failed because another entity of the same type already has the same primary key value.
This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values.
This may be because some entities are new and have not yet received database-generated key values.
In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
The update works fine, when it is the first method you call of the repository.
(I created an entity with an id already in the DB and called the Update.)
The update doesn't work when you do a get of the entity before you update it.
(For example, I get an entity X, convert it to a DTO, then change some values in the UI,
then call a web api that creates an entity X with the new values and
call the Update of the repository.)
Any ideas to avoid this?
When you have a CRUD app, you always call the get before the update.
I'm using my own attach method:
public void Attach<E>(ref E entity)
{
if (entity == null)
{
return;
}
try
{
ObjectStateEntry entry;
bool attach = false;
if (ObjectStateManager.TryGetObjectStateEntry(CreateEntityKey(entitySetName, entity), out entry))
{
attach = entry.State == EntityState.Detached;
E existingEntityInCache = (E)entry.Entity;
if (!existingEntityInCache.Equals(entity))
{
existingEntityInCache.SetAllPropertiesFromEntity(entity);
}
entity = existingEntityInCache;
}
else
{
attach = true;
}
if (attach)
objectContext.AttachTo(entitySetName, entity);
}
catch (Exception ex)
{
throw new Exception("...");
}
}
I had the same issue. The problem was in mixed contexts. When you read entity from DB in context1. Then if you can update this entity with contex2 (other instance of the same context with own entity cache). This may throw an exception.
Pls check for references too:
by context1:
read entity1 with referenced entity2 from DB
by context2:
read entity2 from DB. Then update entity1 (with referenced entity2 from context1).
When you try attach entity1 with referenced entity2 to context2, this throw exception because entity2 already exists in context2.
The solution is use only one context for this operation.
This is my breezeController using EF repository:
[BreezeController]
public class BreezeController : ApiController
{
private readonly MyRepository _repository;
public BreezeController()
{
_repository = new MyRepository(User);
}
[HttpPost]
[ValidateHttpAntiForgeryToken]
public SaveResult SaveChanges(JObject saveBundle)
{
return _repository.SaveChanges(saveBundle);
}
[HttpGet]
public IQueryable<Compound> Compounds(int id)
{
var compounds = new List<Compound>();
compounds.add(new Compound() { Name = "cmp1" });
compounds.add(new Compound() { Name = "cmp2" });
compounds.add(new Compound() { Name = "cmp3" });
// Save compounds to database
return compounds.AsQueryable();
}
}
I'd like to save the compounds created here to database before returning. Should I call SaveChanges? How?
UPDATE:
I tried to bring the objects to client and save. However, I can't seem to use those objects directly as:
cs.compound = compound;
manager.saveChanges();
Because I'm getting this error "Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries". How can I get around this error? I believe I just missed a little tweak.
Instead, I had to create entity as usual, and assign properties one by one like
cs.compound = manager.createEntity("Compound");
cs.compound.name = compound.name;
...
manager.saveChanges();
This is quite cumbersome because I have a lot of properties and nested objects.
So, how can I use the objects created on server to save directly?
I don't have an idea of how you declared the dbContext inside the repository.
Let's say you have it declared this way :
public MyDBContext { get { return _contextProvider.Context; } }
Then you can add the _repository.MyDBContext.SaveChanges();
right before the line
return compounds.AsQueryable();
I have an MVC application that uses Entity Framework 5. In few places I have a code that creates or updates the entities and then have to perform some kind of operations on the updated data. Some of those operations require accessing navigation properties and I can't get them to refresh.
Here's the example (simplified code that I have)
Models
class User : Model
{
public Guid Id { get; set; }
public string Name { get; set; }
}
class Car : Model
{
public Guid Id { get; set; }
public Guid DriverId { get; set; }
public virtual User Driver { get; set; }
[NotMapped]
public string DriverName
{
get { return this.Driver.Name; }
}
}
Controller
public CarController
{
public Create()
{
return this.View();
}
[HttpPost]
public Create(Car car)
{
if (this.ModelState.IsValid)
{
this.Context.Cars.Create(booking);
this.Context.SaveChanges();
// here I need to access some of the resolved nav properties
var test = booking.DriverName;
}
// error handling (I'm removing it in the example as it's not important)
}
}
The example above is for the Create method but I also have the same problem with Update method which is very similar it just takes the object from the context in GET action and stores it using Update method in POST action.
public virtual void Create(TObject obj)
{
return this.DbSet.Add(obj);
}
public virtual void Update(TObject obj)
{
var currentEntry = this.DbSet.Find(obj.Id);
this.Context.Entry(currentEntry).CurrentValues.SetValues(obj);
currentEntry.LastModifiedDate = DateTime.Now;
}
Now I've tried several different approaches that I googled or found on stack but nothing seems to be working for me.
In my latest attempt I've tried forcing a reload after calling SaveChanges method and requerying the data from the database. Here's what I've done.
I've ovewrite the SaveChanges method to refresh object context immediately after save
public int SaveChanges()
{
var rowsNumber = this.Context.SaveChanges();
var objectContext = ((IObjectContextAdapter)this.Context).ObjectContext;
objectContext.Refresh(RefreshMode.StoreWins, this.Context.Bookings);
return rowsNumber;
}
I've tried getting the updated object data by adding this line of code immediately after SaveChanges call in my HTTP Create and Update actions:
car = this.Context.Cars.Find(car.Id);
Unfortunately the navigation property is still null. How can I properly refresh the DbContext immediately after modifying the data?
EDIT
I forgot to originally mention that I know a workaround but it's ugly and I don't like it. Whenever I use navigation property I can check if it's null and if it is I can manually create new DbContext and update the data. But I'd really like to avoid hacks like this.
class Car : Model
{
[NotMapped]
public string DriverName
{
get
{
if (this.Driver == null)
{
using (var context = new DbContext())
{
this.Driver = this.context.Users.Find(this.DriverId);
}
}
return this.Driver.Name;
}
}
}
The problem is probably due to the fact that the item you are adding to the context is not a proxy with all of the necessary components for lazy loading. Even after calling SaveChanges() the item will not be converted into a proxied instance.
I suggest you try using the DbSet.Create() method and copy across all the values from the entity that you receive over the wire:
public virtual TObject Create(TObject obj)
{
var newEntry = this.DbSet.Create();
this.Context.Entry(newEntry).CurrentValues.SetValues(obj);
return newEntry;
}
UPDATE
If SetValues() is giving an issue then I suggest you try automapper to transfer the data from the passed in entity to the created proxy before Adding the new proxy instance to the DbSet. Something like this:
private bool mapCreated = false;
public virtual TObject Create(TObject obj)
{
var newEntry = this.DbSet.Create();
if (!mapCreated)
{
Mapper.CreateMap(obj.GetType(), newEntry.GetType());
mapCreated = true;
}
newEntry = Mapper.Map(obj, newEntry);
this.DbSet.Add(newEntry;
return newEntry;
}
I use next workaround: detach entity and load again
public T Reload<T>(T entity) where T : class, IEntityId
{
((IObjectContextAdapter)_dbContext).ObjectContext.Detach(entity);
return _dbContext.Set<T>().FirstOrDefault(x => x.Id == entity.Id);
}
I'm using Entity Framework 5.0 with DbContext and POCO entities. There's a simple entity containing 3 properties:
public class Record
{
public int Id { get; set; }
public string Title { get; set; }
public bool IsActive { get; set; }
}
The Title field is always unmodified, and the UI simply displays it without providing any input box to modify it. That's why the Title field is set to null when the form is sent to the server.
Here's how I tell EF to perform partial update of the entity (IsActive field only):
public class EFRepository<TEntity>
{
...
public void PartialUpdate(TEntity entity, params Expression<Func<TEntity, object>>[] propsToUpdate)
{
dbSet.Attach(entity);
var entry = _dbContext.Entry(entity);
foreach(var prop in propsToUpdate)
contextEntry.Property(prop).IsModified = true;
}
}
and the call:
repository.PartialUpdate(updatedRecord, r => r.IsActive);
Calling SaveChanges method, I get the DbEntityValidationException, that tells me, Title is required. When I set dbContext.Configuration.ValidateOnSaveEnabled = false, everything is OK.
Is there any way to avoid disabling validation on the whole context and to tell EF not to validate properties that are not being updated?
Thanks in advance.
If you use partial updates or stub entities (both approaches are pretty valid!) you cannot use global EF validation because it doesn't respect your partial changes - it always validates whole entity. With default validation logic you must turn it off by calling mentioned:
dbContext.Configuration.ValidateOnSaveEnabled = false
And validate every updated property separately. This should hopefully do the magic but I didn't try it because I don't use EF validation at all:
foreach(var prop in propsToUpdate) {
var errors = contextEntry.Property(prop).GetValidationErrors();
if (erros.Count == 0) {
contextEntry.Property(prop).IsModified = true;
} else {
...
}
}
If you want to go step further you can try overriding ValidateEntity in your context and reimplement validation in the way that it validates whole entity or only selected properties based on state of the entity and IsModified state of properties - that will allow you using EF validation with partial updates and stub entities.
Validation in EF is IMHO wrong concept - it introduces additional logic into data access layer where the logic doesn't belong to. It is mostly based on the idea that you always work with whole entity or even with whole entity graph if you place required validation rules on navigation properties. Once you violate this approach you will always find that single fixed set of validation rules hardcoded to your entities is not sufficient.
One of things I have in my very long backlog is to investigate how validation affects speed of SaveChanges operation - I used to have my own validation API in EF4 (prior to EF4.1) based on DataAnnotations and their Validator class and I stopped using it quite soon due to very poor performance.
Workaround with using native SQL has same effect as using stub entities or partial updates with turned off validation = your entities are still not validated but in addition your changes are not part of same unit of work.
In reference to Ladislav's answer, I've added this to the DbContext class, and it now removes all the properties that aren't modified.
I know its not completely skipping the validation for those properties but rather just omitting it, but EF validates per entity not property, and rewriting the entire validation process anew was too much of hassle for me.
protected override DbEntityValidationResult ValidateEntity(
DbEntityEntry entityEntry,
IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items);
var falseErrors = result.ValidationErrors
.Where(error =>
{
if (entityEntry.State != EntityState.Modified) return false;
var member = entityEntry.Member(error.PropertyName);
var property = member as DbPropertyEntry;
if (property != null)
return !property.IsModified;
else
return false;//not false err;
});
foreach (var error in falseErrors.ToArray())
result.ValidationErrors.Remove(error);
return result;
}
This is a remix of previous #Shimmy response and it's a version that I currently use.
What I've added is the clause (entityEntry.State != EntityState.Modified) return false; in the Where:
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items);
var falseErrors = result
.ValidationErrors
.Where(error =>
{
if (entityEntry.State != EntityState.Modified) return false;
var member = entityEntry.Member(error.PropertyName);
var property = member as DbPropertyEntry;
if (property != null) return !property.IsModified;
return false;
});
foreach (var error in falseErrors.ToArray())
{
result.ValidationErrors.Remove(error);
}
return result;
}