Is it possible in EntityFramework 6 to add/remove related entities without actual fetching the related entities?
I was trying:
var a = new EntityA()
a.B = new EntityB { Id = 2 };
db.Entry(a).State = EntityState.Added;
db.SaveChanges();
The entity with Id already exists in DB. My attempt fails with the following exception:
Attaching an entity of type 'EntityB' 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.
To solve your exact error you need to set db.Entry( b ).State of the new B entity to Unchanged (your code results in Added)
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.
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();
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!
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".
I already make code in c# like this
IList<BookViewModel> ListBook= _bookService.GetListById(BookViewModel);
foreach (BookViewModel apart in ListBook)
{
apart.Status = "Published";
_bookService.Update(apart);
}
and code update in my repository like this.
public virtual TEntity Update(TEntity updatingObject)
{
this.GetDbSet<TEntity>().Attach(updatingObject);
this.SetEntityState(updatingObject, EntityState.Modified);
this.UnitOfWork.SaveChanges();
return updatingObject;
}
and my method
public IList<BookViewModel> GetListById(BookViewModel bookVM)
{
Expression<Func<Book, bool>> criteria = c => c.IdBook == bookVM.Id Book;
return this.GetList(criteria);
}
but i have error
Attaching an entity of type 'Models.Book' 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.
can any one have suggestion for change code update method, i already read many reference but it's different. thank's!
for this question i have some solution for update entity i use
Entity framework extended.. this already in nugget..
Entity framework Extended