Use AsNoTracking for entities mapped from AutoMapper - c#

I have a situation where I am mapping DTO -> Database Entity using automapper.
var entityObj = _mapper.Map<REQUESTEXT>(reqDTO);
Then I am using entityObj to update the record in the database.
void Update(REQUESTEXT entityObj)
{
_context.REQUESTEXTs.Attach(entityObj); <--- Error
_context.Entry(entityObj).Property(x => x.CUSTOPTIONCD).IsModified = true;
_context.SaveChanges();
}
When i am trying to attach REQUESTEXT object to context, its giving me an error:
Attaching an entity of type 'A' 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.
As per this SO answer: https://stackoverflow.com/a/23228001/1169180 I need to use AsNoTracking(), I am not sure how to use that in AutoMapper?
Any suggestions?

AsNoTracking refers to when the entities are loaded by the context, not Automapper. You are getting the error because at some point in that DbContext's life, it has loaded the entity with that ID and is tracking it. The option they recommended is to change over your entity loading to use AsNoTracking which effectively tells EF not to track the entity when it is read.
An alternative solution to that problem is to check for the existence of the entity in the DbContext's local cache first, and if found, use AutoMapper to map your property changes across to that existing entity, rather than creating a new entity.
For example:
var existingEntity = _context.REQUESTEXTs.Local.SingleOrDefault(x => x.EntityId == reqDTO.EntityId);
if(existingEntity != null)
mapper.Map(reqDto, existingEntity);
else
{
var entityObj = _mapper.Map<REQUESTEXT>(reqDTO);
_context.REQUESTEXTs.Attach(entityObj);
_context.Entry(entityObj).Property(x => x.CUSTOPTIONCD).IsModified = true;
}
_context.SaveChanges();
This checks the local cache for an existing entity, (does not hit DB) and if found, it uses AutoMapper to update it's properties. The entity tracking will note the changes, so when SaveChanges is called, the modifications would go through to the DB. If the local cache does not have the entity, then we create a new instance, attach it, mark it as modified, and save.
One suggestion that appears to be missing in your example: You should be validating the assumptions that:
The ID in your DTO actually does exist in the database before attempting this
and
The record being modified can, and should be editable by the user making this request.
and
The data being updated is fully validated.
If this is a web application /w an accessible Controller action or Web API endpoint, this could be exploitable to allow users to edit records they otherwise should not be able to, or update records in ways they should not be. (Trust nothing from a client request.) Each request should be validated thoroughly, and any deviation detected should terminate the client session.

Related

How to revert changes on detached entities

Here's the flow of the program:
Fetch a list of entities and use them. This will disconnect/detach all entities from the context.
Make changes on one of the entities and save it. I'm loading the entity from the context and apply the changes (scalar properties and relational) of the detached entity to the freshly loaded entity.
I have a feature where the user can revert all changes made on the disconnected entity. Here's the code I'm using:
public async Task RevertChanges()
{
using (var db = new TwinTailDb())
{
//Fansubs.Clear();
if (db.Entry(this).State == EntityState.Detached && Id != 0)
{
db.ArchiveEntries.Attach(this);
await db.Entry(this).ReloadAsync();
}
//await db.Entry(this).Collection(a => a.Fansubs).LoadAsync();
}
}
However, when I attach the detached entity, it throws this exception:
Additional information: Attaching an entity of type 'TwinTail.Entities.ArchiveEntry' 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.
Note that the context is automatically disposed because I'm using the using statement.
I'm not sure why it even there is a conflict in the primary key since I didn't even load another entity since the previous context was already disposed.
Also, if I skip step 2 where I save all changes in the entity, it doesn't throw an exception. I'm left with thinking that somehow it's still being tracked .
EDIT:
Here's what happens when I skip attaching, proving that the entity is really detached.
Additional information: Member 'ReloadAsync' cannot be called for the entity of type 'ArchiveEntry' because the entity does not exist in the context. To add an entity to the context call the Add or Attach method of DbSet.
Seriously, what's happening :(
Some actions on the entities that involve context might change the state to Attached. It could happen when passing an entity to a method in context. Try placing a breakpoint with condition to break when Entity state changes, and make sure that entity does not get attached before you actually call attach, as a side effect of other actions. If that is the case, than you're trying to attach an entity that is already attached, which should cause an exception.
Answering my own question.
The main problem was that the way I handled the lifetime of context is wrong. Also, attaching an entity that was loaded from another context will surely throw an error. Entities that are loaded in a context should only be used in that context.
My context is too short-lived. I'll adjust its lifetime so that there's one context for every transaction (database process).
Here's a very detailed article on how to solve/design your architecture: http://mehdi.me/ambient-dbcontext-in-ef6/
After fixing the architecture, reloading/reverting an entity can simply be done by calling:
DbContext.Entry(entity).Reload();

Unable to attach an existing object in Entity Framework 6

I have a class Customer. I am trying to clone a Customer object and modify it, then I want those modifications to be reflected in the context (database as well). I am using following code to do that.
Customer old = context.Customers.Where(c=>c.CustomerID ==1 ).SingleOrDefault();
Customer m = CustomExtensions.ShallowCopyEntity<Customer>(old);
m.Name = "Modified";
m.MobileNo = "9999999999";
context.Customers.Attach(m);
But its throwing following exception
Attaching an entity of type 'DataBindingSample.Customer'
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 tried changing EntityState to Modified but it didn't work.
Can anyone tell me how to achieve this?
My main goals are
I want to clone (I will use deep clone when necessary) an existing entity
Want to modify the cloned entity (as well as referenced entities - I will use deep clone in this case)
Finally I want to save changes to database
EDIT
As pointed out in this comment i am trying to attach object which aready exists in context. So i can detach it first and then atttach again as shown bellow if attach is compulsory.
Customer old = context.Customers.Where(c=>c.CustomerID ==1 ).SingleOrDefault();
Customer m = CustomExtensions.ShallowCopyEntity<Customer>(old);
m.Name = "Modified789789";
m.MobileNo = "9999999999";
((IObjectContextAdapter)context).ObjectContext.Detach(old);
context.Customers.Attach(m);
context.Entry(m).State = EntityState.Modified;
context.SaveChanges();
Otherwise i can follow 2 options mentioned in this answer.
There are 2 options that I can think of:
Copy the updated values back to the original entity loaded into your DbContext and then save changes.
Updated values of the original entity and then discard them if user canceled the update.
Options 1
Just copy the updated values back to the originally loaded entity. Automapper is your friend in tasks like this. This approach can later be extended to allow user to change a model of your entity and not the data layer object itself (e.g. to expose a limited number of fields that user can edit).
var entity = context.Customers.SingleOrDefault(c => c.CustomerID == 1);
var updatedEntity = CustomExtensions.ShallowCopyEntity<Customer>(old);
updatedEntity.Name = "Modified";
updatedEntity.MobileNo = "9999999999";
entity.Name = updatedEntity.Name;
entity.MobileNo = updatedEntity.MobileNo;
context.SaveChanges();
If you add Automapper nuget, then you mappings (copying) will become much easier:
Mapper.CreateMap<Customer, Customer>();
Mapper.Map(updatedEntity, entity);
And your code will look like:
// Configuring mapping. Needs to be done only once.
Mapper.CreateMap<Customer, Customer>();
var entity = context.Customers.SingleOrDefault(c => c.CustomerID == 1);
// Check if entity is null
var updatedEntity = CustomExtensions.ShallowCopyEntity<Customer>(old);
updatedEntity.Name = "Modified";
updatedEntity.MobileNo = "9999999999";
// Copy the updated values back
Mapper.Map(updatedEntity, entity);
context.SaveChanges();
Options 2
Make changes in the originally loaded entity and discard them if user changed her mind and canceled. See this post and this post on how to do it.
Discarding the whole DbContext might not be a good option in case you still need it (duh).

`Attaching an entity of type failed...` exception while trying to update a class in EF and with a generic repository

I'm using Entity Framework. I want to load an entity, edit it, and save the changes back in the DB. But no matter if I've edited a foreign key property or a simple property, EF gives me the following error:
Attaching an entity of type 'ClassX' 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.
Please note that ClassX is not a direct virtual property of the class that I'm trying to update, instead it's a virtual property in some of the other classes that my class has navigation properties to them.
I've read some related issues. But I didn't really get how I should apply them to my own problem, since I'm using a generic repository as posted below.
public class GenericRepository<T> where T : class
{
private EFDbContext context = new EFDbContext();
public IEnumerable<T> GetAll()
{
return context.Set<T>();
}
public void Insert(T entity)
{
context.Set<T>().Add(entity);
context.SaveChanges();
}
public void Update(T entity)
{
context.Entry(entity).State = System.Data.Entity.EntityState.Modified;
context.SaveChanges();
}
//removed for brevity
}
I've encountered another problem related to virtual properties and I was advised to use ViewModels and Object to Object mapping.
As far as I got it, there's 3 options:
Use ViewModels and object-to-object mapping. I'm not going with this one, it was really painful since o2o mapping libraries have lots of bugs.
Somehow uses reference. But I can't do that since the repository is generic. Maybe I should use reflection API for that?
Delete all virtual properties. It is actually an option, since they're creating more problems than they solve.
Can anyone please explain why this problem happens and what's the easiest way to solve it?
When you set the State of an entity to Modified it also attaches all children (entities referenced by navigation properties) with State == EntityState.Unchanged. If your context already has entities with the same key, it will raise that error.
If you want those entities to be ignored, there are a few options I can think of:
Create a new data context within Update, and don't worry about the children entities because with EntityState.Unchanged, when you call SaveChanges, they'll be ignored. This probably doesn't make sense if you're using some kind of Repository Pattern.
Go through the navigation properties you don't want to attach and set to null before setting State = EntityState.Modified
After setting State = EntityState.Modified, for child entities you want to ignore, set State = EntityState.Detached
Edit
It would also be good to figure out why the context would end up with multiple child entities with the same key in the first place.

EF6 not tracking changes on entities when using FindAsync

I'm using EF6, with a Repository pattern (a repository has its own context instance). When I use FindAsync to obtain and modify an entity, the changes are not tracked, and so any properties that are edited are not saved upon SaveChanges. However, I also expose the table through the repository via IQueryable, and if I obtain an entity that way, all changes are saved properly. I'm trying to figure out why changes are not tracked when I use the FindAsync method.
My Find repo method:
public async Task<CDCRoute> FindDrivingRouteAsync(long routeId, string userId)
{
var route = await routeContext.Routes.FindAsync(routeId);
if (route != null && route.CDCUserInfoId == userId)
{
return route;
}
return null;
}
Table exposed with IQueryable:
public IQueryable<CDCRoute> Routes
{
get { return routeContext.Routes; }
}
Accessing a route via Find (does not save changes when modified):
routeRepo.FindDrivingRouteAsync(message.RouteId, message.UserId);
Accessing a route via the exposed IQueryable (does save changes when modified):
routeRepo.Routes.FirstOrDefault(r => r.RouteId == message.RouteId && r.CDCUserInfoId == message.UserId);
I'm sure I am missing something (am somewhat new to EF), so any help would be greatly appreciated, thanks!
I just had this problem - I suspect it's a bug with EF, since manually updating the EntityState & then forcing the context to save changes, or using the Synchronous version of the same method in the same Microsoft EntityFramework library, caused no issues.
Specifically, when using the Microsoft.AspNet.Identity.EntityFramework library & UserStore/UserManager classes, when an object was retrieved using FindAsync, subsequent modifications were not properly tracked by Entity Framework.
I.e. was getting this error after FindAsync, then trying to update the DB:
Attaching an entity of type 'MyNamespace.MyUser' 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 UpdateAsync method that was failing looked like this:
public async Task UpdateAsync(MyUser user)
{
var result = await _manager.UpdateAsync(user); //State = EntityState.Unchanged
//UpdateAsync above is directly inherited from Microsoft.AspNet.Identity.EntityFramework.UserManager<T>
//ERROR! Primary key duplicate (Default identity method doing Attach :/ )
}
Eventually through tracking I found that the EntityState was still "Unchanged" even though changes were made. I then changed the code to this:
public async Task UpdateAsync(MyUser user)
{
_store.Context.Entry(user).State = EntityState.Modified;
var result = await _store.Context.SaveChangesAsync();
//var result = await _manager.UpdateAsync(user);
}
..the changes were then picked up & auto-magically updated in the DB. A completely synchronous Find & Update (using the same library) also allowed the Entity to be tracked & updated - this solution I deemed less acceptable than a workaround.
I'm not 100% certain it's a bug, but if this is verified by enough people, someone should open a ticket with MSFT.

How do you tell if an EF4 entity is new or an existing record?

In my application I have an entity which is being used essentially as a complex many to many between my User and Project entities. I am trying to determine how to figure out if my service layer needs to add the entity to the context or attach the entity (for updating an existing entity) and I am at a loss of how.
This is easy to determine for most of my entities because of the Int Id field, if it's zero the database hasn't given it an identity value yet. This is not possible if it's a composite primary key.
Does anyone have any suggestion on how to determine if an instance of an entity is new or an update to an existing record?
Edit: I forgot to mention, these entities are POCOs built for code-first, so I don't have an EntityState property on the entity itself.
Yes, as the above answers state, you check the EntityState for the entity in the OSM.
However, keep in mind this only works for entities attached to the context/graph.
I'm currently working with detached entities (ASP.NET MVC), and because they are not attached to the graph, the EntityState is unchanged.
In this case, i am doing a precautionary call to the DB to grab the entity by the key. If nothing is returned, i do an Add, otherwise i use ApplyCurrentValues to override the values, then do .SaveChanges
I'm still wondering if this is the correct way, but thought i'd put it out there.
I'm using POCO's which have no change tracking, hence i have to do a bit more work.
Since there is no EntityState for the POCO, you have to manually call into the OSM:
var pocosInGraph = ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified)
After you Attach/Add, your POCO should be in that collection.
As i said though, if this is for a MVC application, your entities are detached on a HTTP POST, and thus the EntityState will still be unchanged.
In my case, i manually set the EntityState after Attaching:
ctx.Attach(poco);
ctx.ObjectStateManager.ChangeObjectState(poco, EntityState.Modified);
ctx.SaveChanges();
if (x.EntityState == System.Data.EntityState.Added)
//Add
else if (x.EntityState == System.Data.EntityState.Modified)
//Attach
for more info
http://msdn.microsoft.com/en-us/library/system.data.entitystate.aspx

Categories

Resources