Im not EF specialist propably, but I have an issue with it:).
Im doing n-layered business application, so I have Service code and Repository code in my app. In my service code, it read existing User entity, I do update of some properties and it calls Repositiry' method Edit. And there error appears:
Attaching an entity of type
'MobileWallet.Common.Repository.MwbeUserData' 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.
My Edit method looks like this:
public override void Edit(MwbeUserData entityToUpdate)
{
LogChangeTrackerStateValues("UserUpdate starts");
if (Context.Entry(entityToUpdate).State == EntityState.Detached)
{
DbSet.Attach(entityToUpdate);
}
Context.Entry(entityToUpdate).State = EntityState.Modified;
//fix for User.Address problem
Context.Entry(entityToUpdate.Address).State = EntityState.Modified;
LogChangeTrackerStateValues("UserUpdate ends");
}
I also tried code like this:
public override void Edit(MwbeUserData entityToUpdate)
{
Context.Entry(entityToUpdate).State = EntityState.Modified;
//fix for User.Address problem
Context.Entry(entityToUpdate.Address).State = EntityState.Modified;
}
Record which it being udpated is kept in ChangeTracker
Context.ChangeTracker.Entries().ToList()[1]
but Context.Entry(entityToUpdate).State = Detach for this object.
QUESTION 1: How to solve this?
QUESTION 2: Any good tutorial to understand how to work with EF with business n-layered applications?
Thanks for answers.
UPDATE 1:
New findings:
In Repository Edit method:
Context.ChangeTracker.Entries().ToList()[1].CurrentValues["Firstname"] = "AAA"
Context.ChangeTracker.Entries().ToList()[1].OriginalValues["Firstname"]= "AAA"
but CurrentValue should be BBB, why is not updated? it was updated in Service code which calls Respority Code and updated entity is passed to Repository Edit method.
UPDATE 2:
More about my architecture: I have 3 layes Controler(WEB API), Service and Repository. So my Service method update looks like this:
public bool UpdateUser(MwbeUserUpdateIn userUpdateData)
{
MwbeReturnData<MwbeUserData> userData = repository.Get(userUpdateData.UserId);
// Determine if user exists
if (MwbeResponseCodes.NotFound == userData.Code)
{
return false;
}
MwbeUserData user = userData.Data;
// Check each field to be updated
if (!String.IsNullOrEmpty(userUpdateData.FirstName))
{
user.Firstname = userUpdateData.FirstName;
}
if (!String.IsNullOrEmpty(userUpdateData.MiddleName))
{
user.Middlename = userUpdateData.MiddleName;
}
if (!String.IsNullOrEmpty(userUpdateData.LastName))
{
user.Secondname = userUpdateData.LastName;
}
if (!String.IsNullOrEmpty(userUpdateData.MobileNumber))
{
user.Mobilenumber = userUpdateData.MobileNumber;
}
if (!String.IsNullOrEmpty(userUpdateData.Email))
{
user.Email = userUpdateData.Email;
}
if (null != userUpdateData.BirthDate)
{
user.BirthDate = (DateTime)userUpdateData.BirthDate;
}
// Update Addres fields
if (null != userUpdateData.Address)
{
if (!String.IsNullOrEmpty(userUpdateData.Address.City))
{
user.Address.City = userUpdateData.Address.City;
}
if (!String.IsNullOrEmpty(userUpdateData.Address.Country))
{
user.Address.Country = userUpdateData.Address.Country;
}
if (!String.IsNullOrEmpty(userUpdateData.Address.Street))
{
user.Address.Street = userUpdateData.Address.Street;
}
if (!String.IsNullOrEmpty(userUpdateData.Address.ZipCode))
{
user.Address.ZipCode = userUpdateData.Address.ZipCode;
}
}
// Save changes to DB
repository.Edit(ref user);
repository.SaveChanges();
return true;
}
Instead of calling Context.Entry(entityToUpdate) and setting it state to modified you could search for the entity you want to update and modify its members. Then set its state as modified, like this.
Also in your Edit function you should explicitly list which members of your MwbeUserData object your are updating. This is a security issue, it will prevent someone over posting extra members to your controller.
Here is an example of a edit function I have used.
public ActionResult Edit([Bind(Include = "Id,CompanyName,Abbreviation,CompanyTypeRef")] Company company)
{
if (!ModelState.IsValid) return View(company);//error invalid state
var c = Entities.Set<Company>().FirstOrDefault(x => x.Id == company.Id);
if(c == null) return View(company);// error could not find entity
Entities.Entry(c).CurrentValues.SetValues(company);
Entities.Entry(c).State = EntityState.Modified;
Entities.SaveChanges();
return RedirectToAction("Edit", "Company", new { id = company.Id });
}
Update:
I had a similar issue with entity changes not being recorded by changes tracker in EntityFramework.Extented and it was fix by updating the entities CurrentValues with this line.
Entities.Entry(c).CurrentValues.SetValues(company);
Related
I would like to delete a record using the entity framework. DB is oracle.
Approach 1:
public void DeleteTask(Guid taskId, string userId)
{
var task = _context.TWFITSKs.FirstOrDefault(x => x.ID == taskId.ToString());//<---Error line
if (task == null) return;
_context.TWFITSKs.Attach(task);
_context.TWFITSKs.Remove(task);
_context.SaveChanges();
}
Error : ORA-00932: inconsistent datatypes: expected - got CLOB
TWFITSK does contain a column with datatype as CLOB, but not sure why that is causing a problem in this select statement.
Approach 2:
public void DeleteTask(Guid taskId, string userId)
{
var task = new TWFITSK { ID = taskId.ToString() };
_context.TWFITSKs.Attach(task); // <--- Error line
_context.TWFITSKs.Remove(task);
_context.SaveChanges();
}
Error: System.InvalidOperationException: 'Attaching an entity of type
'XXXXX.TWFITSK' 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.'
Approach 3:
public void DeleteTask(Guid taskId, string userId)
{
var task = new TWFITSK { ID = taskId.ToString() };
_context.TWFITSKs.Remove(task); //<--- Error line
_context.SaveChanges();
}
Error: The object cannot be deleted because it was not found in the
ObjectStateManager
You could try changing the entity state to deleted:
var task = new TWFITSK { ID = taskId.ToString() };
_context.Entry(task).State = EntityState.Deleted;
_context.SaveChanges();
Update: try passing the entity to your method as it sounds like its already attached to the context. This may work:
public void DeleteTask(TWFITSKs task)
{
if (task == null) return;
_context.TWFITSKs.Remove(task);
_context.SaveChanges();
}
Why don't you just call .Remove
var task = new TWFITSK { ID = taskId.ToString() };
_context.TWFITSKs.Entry(task).State = EntityState.Deleted;
_context.SaveChanges();
but this may still won't work if you have datatype mismatch. It may be better If you can share table DDL script, class definition and OnModelCreating
I have a huge form with many child entities, so, I fill EF object with tree using Automapper. Then I want to update similar entities in DB. Any way to attach its to context?
I try to do it by way:
// ApplicationDriver has many child objects
Infrastructure.Asset.ApplicationDriver driver = mapper.Map<Infrastructure.Asset.ApplicationDriver>(model);
// get similar object from DB
Infrastructure.Asset.ApplicationDriver currentApplication = db.ApplicationDrivers.Where(p => p.ApplicationId == model.ApplicationId).FirstOrDefault();
if (currentApplication == null)
{
db.ApplicationDrivers.Add(driver);
}
else
{
// try to attach driver to current context.
// I want to 'replace' current object with all child objects in DB
db.ApplicationDrivers.Attach(driver);
currentApplication = driver;
}
await db.SaveChangesAsync();
I get an error:
"Attaching an entity of type 'Infrastructure.Asset.ApplicationDriver'
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."
I try:
var currentApplication = db.ApplicationDrivers.Where(p => p.ApplicationId == model.ApplicationId).FirstOrDefault();
if (currentApplication == null)
{
db.ApplicationDrivers.Add(driver);
}
else
{
driver.Id = currentApplication.Id;
db.ApplicationDrivers.Attach(driver);
}
await db.SaveChangesAsync();
and get the same error. What is incorrect and how to solve this problem without manual copying each property for all child objects from driver to currentApplication?
No need to attach to entity, if you want to update the DB, and replace the db with the current object :
// ApplicationDriver has many child objects
Infrastructure.Asset.ApplicationDriver driver = mapper.Map<Infrastructure.Asset.ApplicationDriver>(model);
// get similar object from DB
Infrastructure.Asset.ApplicationDriver currentApplication = db.ApplicationDrivers.Where(p => p.ApplicationId == model.ApplicationId).FirstOrDefault();
if (currentApplication == null)
{
db.ApplicationDrivers.Add(driver);
}
else
{
//this will attach, and set state to modified
//same as previous question, make sure you virtual properties are not populated to avoid unwanted duplicates.
db.Entry(driver).State = System.Data.Entity.EntityState.Modified;
currentApplication = driver;
}
await db.SaveChangesAsync();
I have found an article:
https://www.devtrends.co.uk/blog/stop-using-automapper-in-your-data-access-code
seems, it would better don't use automapper to map DTO to EF for edit case..
Why is the attach of an entity throwing an exception if it is right after a query on the same db entity.
Attaching an entity of type 'Article' 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.
Removing the query before the attach statement makes it work as expected.
My controller:
public JsonResult Update(Article entity)
{
JsonResult jRes = new JsonResult();
ArticleDA repo = new ArticleDA();
jRes.Data = repo.Update(entity);
return jRes;
}
My Model
public Result Update(Article entity)
{
Result result = new Result();
using (Entities entities = new Entities())
{
try
{
var article = entities.Article.Where(x => x.id == entity.id).FirstOrDefault();
entities.Article.Attach(entity);
entities.Entry(entity).State = EntityState.Modified;
entities.SaveChanges();
result.OK = true;
}
catch (Exception err)
{
result.OK = false;
}
}
return result;
}
I know the query before the attach statement doesn't make sense, but I was just wondering why querying the db before the attach throws an exception.
Is it because querying the db for an entity with that id makes it available to the EntityFramework?
Please can you explain why this happens?
If you only want to pass over the error, try var article = entities.Article.AsNoTracking().Where(x => x.id == entity.id).FirstOrDefault();
If you want to know why this is happening, wait for my complete answer
I am trying to update a record using Entity Framework 6, code-first, no fluent mapping or a tool like Automapper.
The entity(Employee) has other composite properties associated with it like Addreess(collection), Department
It is also inherited from a base called User
The save method is as follows, with _dbContext being the DbConext implementation
public bool UpdateEmployee(Employee employee)
{
var entity = _dbContext.Employees.Where(c => c.Id == employee.Id).AsQueryable().FirstOrDefault();
if (entity == null)
{
_dbContext.Employees.Add(employee);
}
else
{
_dbContext.Entry(employee).State = EntityState.Modified; // <- Exception raised here
_dbContext.Employees.Attach(employee);
}
return _dbContext.SaveChanges() > 0;
}
I keep getting the error:
Attaching an entity of type 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.
I have tried the following:
Attaching before setting to EntityState.Modified
Adding AsNoTracking() on querying if the object exists(No exception but DB is not updated) - https://stackoverflow.com/a/23228001/919426
Saving using the base entity _dbContext.Users instead of the Employee entity - https://stackoverflow.com/a/25575634/919426
None of which is working for me now.
What could I have gotten wrong for some of those solutions not to work in my situation?
EF already includes a way to map properties without resorting to Automapper, assuming you do not have navigation properties to update:
public bool UpdateEmployee(Employee employee)
{
var entity = _dbContext.Employees.Where(c => c.Id == employee.Id).AsQueryable().FirstOrDefault();
if (entity == null)
{
_dbContext.Employees.Add(employee);
}
else
{
_dbContext.Entry(entity).CurrentValues.SetValues(employee);
}
return _dbContext.SaveChanges() > 0;
}
This usually generates a better SQL statement since it will only update the properties that have changed.
If you still want to use the original method, you'll get rid of entity from the context, either using AsNoTracking (not sure why it didn't update in your case, it should have no effect, so the problem might be something else) or as modifying your query to prevent it from materializing the entity in the first place, using something like bool exists = dbContext.Employees.Any(c => c.Id == employee.Id) for example.
This worked for myself
var aExists = _db.Model.Find(newOrOldOne.id);
if(aExists==null)
{
_db.Model.Add(newOrOldOne);
}
else
{
_db.Entry(aExists).State = EntityState.Detached;
_db.Entry(newOrOldOne).State = EntityState.Modified;
}
I've encountered the same thing when using a repository and unit of work pattern (as documented in the mvc4 with ef5 tutorial).
The GenericRepository contains an Update(TEntity) method that attempts to Attach then set the Entry.State = Modified. The up-voted 'answer' above doesn't resolve this if you are going to stick to the uow / repo pattern.
I did attempt to use the detach process prior to the attach, but it still failed for the same reason as indicated in the initial question.
The reason for this, it turns out, is that I was checking to see if a record existed, then using automapper to generate an entity object from my dto prior to calling update().
By checking for the existance of that record, i put the entity object in scope, and wasn't able to detach it (which is also the reason the initial questioner wasn't able to detach)... Tt tracked the record and didn't allow any changes after I automapper'ed the dto into an entity and then attempted to update.
Here's the generic repo's implementation of update:
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
This is my PUT method (i'm using WebApi with Angular)
[HttpPut]
public IHttpActionResult Put(int id, Product product)
{
IHttpActionResult ret;
try
{
// remove pre-check because it locks the record
// var e = unitOfWork.ProductRepository.GetByID(id);
// if (e != null) {
var toSave = _mapper.Map<ProductEntity>(product);
unitOfWork.ProductRepository.Update(toSave);
unitOfWork.Save();
var p = _mapper.Map<Product>(toSave);
ret = Ok(p);
// }
// else
// ret = NotFound();
}
catch (DbEntityValidationException ex)
{
ret = BadRequest(ValidationErrorsToMessages(ex));
}
catch (Exception ex)
{
ret = InternalServerError(ex);
}
return ret;
}
As you can see, i've commented out my check to see if the record exists. I guess i'll see how it works if I attempt to update a record that no longer exists, as i no longer have a NotFound() return opportunity.
So to answer the initial question, i'd say don't look for entity==null before making the attempt, or come up with another methodology. maybe in my case, i could dispose of my UnitOfWork after discovery of the object and then do my update.
You need to detach to avoid duplicate primary key exception whist invoking SaveChanges
db.Entry(entity).State = EntityState.Detached;
I'm having some trouble using a helper method to perform an update to a set of model objects. The table uses a lookup table to hold 5 records per agent/user. If I want to save the record for the agent, I need to save that record onto the AgentTransmission table, and up to 5 other records on the RelationshipCodeLookup table.
Since I have to do this five times per agent, and we must do the process in the Create and Edit methods, I created a helper method to save the records. This works fine during the create process as we're simply doing a DbContext.Add(). However when I need to perform an update, I get the error message
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
I think this has to do with the fact I'm passing the model object to my helper method, and therefore the DbContext thinking that it has two separate objects to keep track of. I say this because the lines of code that are commented out work just fine and allow me to save the object. Passing the object to the helper method, however, gets the above error.
Does anyone know of a way around this (using a helper method to perform an update)?
Controller Action
//Save relationship codes in lookup table
if (AgentTransmissionValidator.ValidateRelationshipCode(agenttransmission.RelationshipCode1))
{
//db.Entry(agenttransmission.RelationshipCode1).State = EntityState.Modified;
//db.SaveChanges();
SaveRelationshipCodes(agenttransmission.RelationshipCode1, agenttransmission.ID);
}
if (AgentTransmissionValidator.ValidateRelationshipCode(agenttransmission.RelationshipCode2))
{
//db.Entry(agenttransmission.RelationshipCode1).State = EntityState.Modified;
//db.SaveChanges();
SaveRelationshipCodes(agenttransmission.RelationshipCode2, agenttransmission.ID);
}
if (AgentTransmissionValidator.ValidateRelationshipCode(agenttransmission.RelationshipCode3))
{
//db.Entry(agenttransmission.RelationshipCode1).State = EntityState.Modified;
//db.SaveChanges();
SaveRelationshipCodes(agenttransmission.RelationshipCode3, agenttransmission.ID);
}
if (AgentTransmissionValidator.ValidateRelationshipCode(agenttransmission.RelationshipCode4))
{
//db.Entry(agenttransmission.RelationshipCode1).State = EntityState.Modified;
//db.SaveChanges();
SaveRelationshipCodes(agenttransmission.RelationshipCode4, agenttransmission.ID);
}
if (AgentTransmissionValidator.ValidateRelationshipCode(agenttransmission.RelationshipCode5))
{
//db.Entry(agenttransmission.RelationshipCode1).State = EntityState.Modified;
//db.SaveChanges();
SaveRelationshipCodes(agenttransmission.RelationshipCode5, agenttransmission.ID);
}
Helper Method
public void SaveRelationshipCodes(RelationshipCodeLookup relCode, int id)
{
if (relCode.AgentId == 0) relCode.AgentId = id;
relCode.LastChangeDate = DateTime.Now;
relCode.LastChangeId = Security.GetUserName(User);
//Check to see if record exists and if not add it
if (db.RelationshipCodeLookup.Find(id, relCode.RelCodeOrdinal) != null)
{
db.Entry(relCode).State = EntityState.Detached;
}
else
{
if(relCode.RelCodeOrdinal == 0) relCode.RelCodeOrdinal = FindOrdinal(relCode);
db.RelationshipCodeLookup.Add(relCode);
}
db.SaveChanges();
}
EDIT
After scouring the web I attempted to save via this method
//Check to see if record exists and if not add it
if (db.RelationshipCodeLookup.Find(id, relCode.RelCodeOrdinal) != null)
{
db.Entry(relCode).CurrentValues.SetValues(relCode);
}
else
{
Member 'CurrentValues' cannot be called for the entity of type 'RelationshipCodeLookup because the entity does not exist in the context. To add an entity to the context call the Add or Attach method of DbSet<RelationshipCodeLookup>
However.... doing that only puts me back at the start with the following error on db.RelationshipCodeLookup.Attach(relCode);
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Try this:
db.RelationshipCodeLookup.Attach(relCode);
db.Entry(relCode).State = EntityState.Modified;
For updates you want to attach the detached object then set it's state to modified.
The issue here seems to be that the Entity Framework cannot track two objects of the same kind at the same time. Because of that I find the solution to this problem more than a little weird. By calling .Find() on the DbContext and instantiating a second copy of the model object I was finally able to save. Seems to break all the rules the EF was laying out for me in the error messages, but hey it works.
public void SaveRelationshipCodes(int id, RelationshipCodeLookup relCode)
{
if (relCode.AgentId == 0) relCode.AgentId = id;
relCode.LastChangeDate = DateTime.Now;
relCode.LastChangeId = Security.GetUserName(User);
//Check to see if record exists and if not add it
if (db.RelationshipCodeLookup.Find(id, relCode.RelCodeOrdinal) != null)
{
//Need to call .Find to get .CurrentValues method call to work
RelationshipCodeLookup dbRelCode = db.RelationshipCodeLookup.Find(id, relCode.RelCodeOrdinal);
db.Entry(dbRelCode).CurrentValues.SetValues(relCode);
}
else
{
if(relCode.RelCodeOrdinal == 0) relCode.RelCodeOrdinal = FindOrdinal(relCode);
db.RelationshipCodeLookup.Add(relCode);
}
db.SaveChanges();
}