Attach entities with tree to EF - c#

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..

Related

deleting record from oracle database using entity framework

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

Entity framework Attach error after a query

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

EF: how to modify existing entry in DB in N-layered application

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);

Check if entity is being tracked by Entity Framework context

I have a code block which checks whether an entity is being tracked by my context. If it is, I need to detach it. This works for a given T type.
public virtual async Task<bool> InsertOrUpdate(TE entity)
{
if (entity.Id == 0 || entity.Id == ModelState.New)
{
// attach new entity
_context.Entry(entity).State = EntityState.Added;
}
else
{
// Sometimes when you want to update a detached entity, before attempting to attach it (by setting the .State property),
// you first need to make sure the entity isn't already attached and being tracked. If this is the case, the existing entity
// needs to be detached, and the updated entity, attached.
var attachedEntity = _context.ChangeTracker.Entries<TE>().FirstOrDefault(e => e.Entity.Id == entity.Id);
if (attachedEntity != null)
{
// the entity you want to update is already attached, we need to detach it and attach the updated entity instead
_context.Entry<TE>(attachedEntity.Entity).State = EntityState.Detached;
}
_context.Entry<TE>(entity).State = EntityState.Modified; // Attach entity, and set State to Modified.
_context.Entry<TE>(entity).Property(o => o.CreatedUserId).IsModified = false;
_context.Entry<TE>(entity).Property(o => o.CreatedDate).IsModified = false;
}
return await _context.SaveChangesAsync() > 0;
}
I now need to change the method so that it fetches all objects of type IEntity within the given T entity parameter and then do the same logic for each object found, but I'm having trouble with setting the ChangeTracker.Entries as I need to set the T type to the current selected type within the foreach. I have no idea how to do this.
public virtual async Task<bool> InsertOrUpdate(TE entity)
{
//// Find all instances of IEntity within TE:
//// * IF entity is new we set State to EntityState.Added (INSERT)
//// * IF entity is existing, we set State to EntityState.Modified (UPDATE)
List<IEntity> found = FindAllInstances<IEntity>(entity);
foreach (IEntity ent in found)
{
if (entity.Id == 0 || entity.Id == ModelState.New)
{
// attach new entity
_context.Entry(entity).State = EntityState.Added;
}
else
{
// Sometimes when you want to update a detached entity, before attempting to attach it (by setting the .State property),
// you first need to make sure the entity isn't already attached and being tracked. If this is the case, the existing entity
// needs to be detached, and the updated entity, attached.
var attachedEntity = _context.ChangeTracker.Entries<TE>().FirstOrDefault(e => e.Entity.Id == entity.Id);
if (attachedEntity != null)
{
// the entity you want to update is already attached, we need to detach it and attach the updated entity instead
_context.Entry<TE>(attachedEntity.Entity).State = EntityState.Detached;
}
_context.Entry<TE>(entity).State = EntityState.Modified; // Attach entity, and set State to Modified.
_context.Entry<TE>(entity).Property(o => o.CreatedUserId).IsModified = false;
_context.Entry<TE>(entity).Property(o => o.CreatedDate).IsModified = false;
}
}
return await _context.SaveChangesAsync() > 0;
}
You could use the inner context wich is an ObjectContext.
var ctx = ((IObjectContextAdapter)_context).ObjectContext;
And then call ctx.Detach() on whatever entity you want. Fortunately this is not a generic method.
You can also get a reference to an ObjetStateManager from the ObjectContext and use it to do whatever state change you want.
More informations:
https://msdn.microsoft.com/en-us/library/system.data.objects.objectcontext.objectstatemanager(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.data.objects.objectstatemanager(v=vs.110).aspx

Updating existing data in EF 6 throws exception - "...entity of the same type already has the same primary key value."

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;

Categories

Resources