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();
Related
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.
I'm using EF code first to control my data. I have two models, ElectricitySite and ElectricitySiteSplit.
ElectricitySite contains List<ElectricitySiteSplits> ElectricitySiteSplits, this is a one to many relationship.
I'm trying to write my Update method for the repository layer which will deal with both the tables, so far I have:
public void UpdateElectricitySite(ElectricitySite updatedElectricitySite)
{
var dbElectricitySite = GetElectricitySite(updatedElectricitySite.ElectricitySiteId);
_context.ElectricitySites.Attach(updatedElectricitySite);
_context.Entry(updatedElectricitySite).State = EntityState.Modified;
_context.SaveChanges();
}
I get the following error when I click the Save button:
Attaching an entity of type
'MySolution.Repo.ElectricityModels.ElectricitySiteSplit' 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 think this is because I've not attached my ElectricitySiteSplit entity however if I add this in below my ElectricitySites attachment:
_context.ElectricitySiteSplits.Attach(updatedElectricitySite.SiteSplits);
I get this error:
Severity Code Description Project File Line Suppression State
Error CS1503 Argument 1: cannot convert from
'System.Collections.Generic.List'
to 'UtilityBilling.Repo.ElectricityModels.ElectricitySiteSplit'
How do I handle the ElectricitySiteSplits table update which is contained in updatedElectrcitiySite.SiteSplits as a list.
FYI - I've already looked here:
Entity Framework 5 Updating a Record
https://msdn.microsoft.com/en-gb/data/jj592676.aspx
EF does not handle both tables automatically. I have to specify which item was added/updated/removed. Take a look at this thread Cleanly updating a hierarchy in Entity Framework
Hope this helps!
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.
An exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll but was not handled in user code
Additional information: Attaching an entity of type
'WebLanguageTeacher.Models.MyDatabase.Word' 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.
This is how it looks like:
var getWordfromDB = db.Words.Find(word.ID);
word.NextReview = getWordfromDB.NextReview;
word.LastReviewed = getWordfromDB.LastReviewed;
db.Entry(word).State = EntityState.Modified;
I'm making some changes in "Word" object. For some reasons I have to get data from database and put it again to the same object.
Without using Find() method, EntityState.Modified works, but when I placed it there, it throws this error.
How to fix that?
The problem appeared because of my wrong understanding of what exactly db.[class].Find() does.
I thought that it simply copies object to the variable, but it's not true. This works as something like link to one of the records in database. The "Word" was somehow marked as "currently modified" so any other attempts to reach this database record was rejected.
I don't know if what I'm saying is correct, but I realized that while debugging, through "trial and error".
Recently I've talked to a Java programmer who told me that Hibernate is smart enough to detect if an entity has any changes made to it and even if you call an UPDATE for entity if it has not been modified then no SQL UPDATE command will be executed.
I am not working with large volumes of data but still I'm curious if the same applies for Entity Framework 5. In my current project I use Repositories so the Update method looks like this:
public virtual void Update(TEntity entity)
{
dbSet.Attach(entity);
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();
}
this combined with UnitOfWork let's say I've just submitted to the controller a List of type News where News is an entity. So if in my Action I do something like this :
public ActionResult Edit(List<News> model)
{
foreach (News item in model)
{
unitOfWork.NewsRepository.Update(item);
}
}
what will actually happen if I have items that have not been modified? Do I still make a database query to update the record with the same data or the entity framework is smart enough to just skip it and proceed with the next item?
In order to get the change set which will project to actual queries to database you can either query underlying context or the entity itself ( if it is self tracking entity). (See my answer to this question).
From your example it is not clear if you are using POCO or self-tracking entities, but in both those cases you are missing step of figuring out what was changed in the object when you attach it.
In order to do this, EF should either query db for original object and compare with the one attached, or if it is self-tracking entity, EF will query inner entity changeset to do the update.
UPDATE: In order to sum up what #hvd and I said:
Since we are talking about POCO entities here is what happens:
Create new context and load entity.
Changes are made to the object - context still hold the changeset
Dispose context, so changeset is lost
New context is created and entity is attached to context with EntityState set to Modified, so context belives all fields are modified
There are several things you can do
switch to self-tracking entities
serialize-deserialize context so changeset will persist
requery database for original object
keep original object somewhere and update it instead of just attaching changed and setting EntityState to Modified, ie do changes within context according to changed entity.
There is this course on Pluralsight ( and there are two newer versions of it with different client side technology stack) which covers this topics and shows how to do change tracking in case you have to change entities out of context.
Entity Framework tracks changes made to objects while they are attached to a context, and will issue UPDATE statements if you've made changes to the properties, and call SaveChanges, without needing to set State to EntityState.Modified manually.
In your code, you're making the changes when the objects aren't attached to a context. The only way Entity Framework can let this work is by forcing an UPDATE statement that (re-)sets all fields in the database, even those that remain unchanged.
So, yes, in general, the same does apply to Entity Framework, just not how you're using it.