I've got an aggregate for a specific type of entity which is stored in a collection inside the aggregate. Now I'd like to add a new entry of that type and update the aggregate afterwards, however Entity Framework never updates anything!
Model
public class MyAggregate {
protected ICollection<MyEntity> AggregateStorage { get; set; }
public void AddEntity(MyEntity entity) {
// some validation
AggregateStorage.Add(entity);
}
}
API Controller
[UnitOfWork, HttpPost]
public void UpdateMyEntity(int aggregateId, MyEntityDto dto) {
var aggregate = _aggregateRepository.Find(aggregateId);
aggregate.AddEntity(...// some mapping of the dto).
_aggregateRepository.Update(aggregate);
}
EF Update
EntitySet.Attach(aggregate);
Context.Entry(aggregate).State = EntityState.Modified;
(Please note that there's an unit of work interceptor on the API action who fires DbContext.SaveChanges() after successful execution of the method.)
Funny thing is, the update never get's executed by EF. I've added a log interceptor to the DbContext to see what's going on sql-wise and while everything else works fine, an update statement never occurs.
According to this answer in detached scenario (either aggregate is not loaded by EF or it is loaded by different context instance) you must attach the aggregate to context instance and tell it exactly what did you changed, set state for every entity and independent association in object graph.
You must either use eager loading and load all data together at the beginning and
instead of changing the state of aggregate, change the state of entities:
foreach(var entity in aggregate.AggregateStorage)
{
if(entity.Id == 0)
Context.Entry(entity).State = EntityState.Added;
}
Related
This question already has answers here:
The instance of entity type cannot be tracked because another instance of this type with the same key is already being tracked
(23 answers)
Closed last month.
I have a model class Patient:
[Key]
[Required(ErrorMessage = "")]
[MinLength(3)]
public string PatientCode { get; set; }
[Required(ErrorMessage = "")]
[MinLength(1)]
public string Name { get; set; }
[Required(ErrorMessage = "")]
[MinLength(1)]
public string Surname { get; set; }
I have a generic repository where I try to update my entity:
public async Task<bool> UpdateAsync(T entity)
{
try
{
_applicationContext.Set<T>().Update(entity);
await _applicationContext.SaveChangesAsync();
return true;
}
catch (Exception)
{
return false;
}
}
which I call from my controller:
private readonly IRepository<Patient> _patientRepository;
...
[HttpPatch("update/")]
public async Task<IActionResult> UpdateAsync([FromBody] Patient patient)
{
var result = await _patientRepository.UpdateAsync(patient);
if (result)
return Ok();
return BadRequest();
}
And when I do so, I get this error:
The instance of entity type 'Patient' cannot be tracked because another instance with the same key value for {'PatientCode'} is already being tracked
If I comment out setting the entity in my repository (comment line _applicationContext.Set<T>().Update(entity);) everything works just fine. It means that my entity has been attached to the ChangeTracker somehow. But how is it possible in such a straight forward code?
The error occurs when a DbContext instance is given an entity reference that it isn't tracking that has a same key value with an entity reference that it is tracking. This can be a common problem in application designs that use Dependency Injection for the DbContext since within the scope of a single repository that gets an injected DbContext it's an unknown what entity references that DbContext might already be tracking. For instance if your code happens to load a different entity that happens to have a reference to that particular Patient record that is either eager or lazy loaded, then passing a different entity reference, such as one that has been deserialized/mapped from a POST payload and calling Update will result in this exception, and it can be situational depending on whether the DbContext happens to be tracking that matching entity or not.
Passing entities around beyond the scope of their DbContext including serialization/deserialization requires extra care when dealing with updating entities and references to related entities. It also means you need to be prepared to deal with tracked references.
Since different entities will likely use different keys, I strongly recommend moving away from the Generic Repository pattern, or at least mitigate it to truly identical lowest-common-denominator type base code. Too many developers end up painting themselves into a tight corner far from the doorway with this anti-pattern.
public async Task<bool> UpdateAsync(Patient patient)
{
try
{ // Going to .Local checks for tracked references, it won't hit the DB.
var trackedReference = _applicationContext.Patients.Local
.SingleOrDefault(p => p.PatientCode == patient.PatientCode);
if (trackedReference == null)
{ // We aren't already tracking an entity so we should be safe to attempt an Update.
_applicationContext.Patients.Update(patient);
}
else if (!Object.ReferenceEquals(trackedReference, patient))
{ // Here we have a bit more work to do. The DbContext is already
// tracking a reference to this Patient and it's *not* the copy
// you are passing in... Need to determine what to do, copy
// values across, etc.
Mapper.Map(patient, trackedReference); // This would use Automapper to copy values into the tracked reference, overwriting.
}
// If the patient passed in is the same reference as the tracked
// instance, nothing to do, just call SaveChanges.
await _applicationContext.SaveChangesAsync();
return true;
}
catch (Exception)
{
return false;
}
}
This may look simple enough, but you need to apply these checks to all entities in the entity graph. So for instance if a Patient references other entities like Address etc. you need to check the DbContext to see if it is already tracking a reference (where that address might be updated or inserted). For references to existing data rows you will want to check and replace references with existing tracked references, or Attach them if not tracked to avoid EF attempting to treat the untracked reference as a new entity and try inserting it.
I am working on WPF Application using MVVM pattern, also I use EF code first for database entities. and because of MVVM I use AutoMapper to map objects from Database "Models" to "ViewModels".
When I update an entity I have to map it back from view model to model and pass it to the update method
public void Update(TEntity entity)
{
TEntity databaseEntity = Context.Set<TEntity>().Find(entity.Id);
DbEntityEntry dbEntityEntry = Context.Entry(databaseEntity);
dbEntityEntry.CurrentValues.SetValues(entity);
dbEntityEntry.State = EntityState.Modified;
}
The problem here I have to Get the entity each time I want to update it, and if I have many objects to update it will cause performance issue. I tried to use this following code but it didn't work because the auto mapper create new instance of the entity different than the one attached with EF dbConext.
public void Update(TEntity entity)
{
DbEntityEntry<TEntity> dbEntityEntry = Context.Entry(entity);
dbEntityEntry.State = EntityState.Modified;
}
I tried to handle this by overriding Equals and GetHashCode methods but it also didn't work.
Is there any way to handle update entity without getting it form the database?
Thank you.
Since you're manually tracking changes you should not retain a reference to the same dbcontext. Read your data inside a Using statement which news up and disposes the dbcontext.
When you write the data back new up an entity, set the properties to those from your viewmodel, set it's state to modified and savechanges.
Again, open and close your dbcontext round this.
There is then no need to go find the entity you read.
Here's a trivial example in case that explanation was unclear.
This does not represent a best practice enterprise solution - I would expect at least a generic updateentityasync method.
We're updating a Person in a People table.
public async Task<bool> UpdatePersonAsync(Person person)
{
int numUpdated = 0;
using (var db = new AndyPeopleContext())
{
db.People.Add(person);
db.Entry(person).State = EntityState.Modified;
numUpdated = await db.SaveChangesAsync();
}
return numUpdated == 1;
}
I have an entity class Timecard, I get this entity from this method:
public Timecard GetTimeCardForPerson(long timecardId)
{
return timecardContext.First(item => item.TimeCardId = timeCardId);
}
timecardContext is of type TimecardContext: DbContext.
I later make a change to the Timecard entity, the Timecard entity has a property:
public virtual ICollection<TimecardRow> TimeCardRows { get; set; }
which is initialized, in Timecard's constructor to a HashSet like so:
this.TimeCardRows = new HashSet<TimecardRow>();
I add a child TimecardRow I then call another method and this is its exact implementation and pass it the same Timecard instance as is returned from GetTimeCardForPerson:
public void SaveTimecard(Timecard timeCard)
{
timecardContext.Entry(timeCard).State = timeCard.TimecardId == 0
? EntityState.Added
: EntityState.Unchanged;
timecardContext.SaveChanges();
}
The passed in Timecard timeCard argument is not attached to the timecardContext and has a TimecardId > 0.
I am surprised that my new TimecardRow saves successfully as Entry(timeCard.State) is set to EntityState.Unchanged.
The EntityState.Unchanged tells my timecardContext that there is nothing to change, does it not? But all the same, the TimecardRow I added is inserted into the database when the SaveTimecard method is called.
The EntityState.Unchanged tells the context that nothing has changed for the Timecard entity.
The TimecardRow is a separate entity which EF will track separately so a call to SaveChanges will insert that entity.
The above assumes that the Timecard is already attached when passed to the Save method (which it will be if it's the same instance returned from the GetTimeCardForPerson method).
If the Id check in the Save method is there to cope with both detached and attached entities, would it be better to leave the state alone unless it is an id of 0?
I'm using Entity Framework 6 (using generated models and DbContext) for my project. The architecture I'm using in my program is that the data access layer is abstracted from the application. This means my entities will mostly stay in detached state.
It seems that reattaching any previously connected entities seems impossible (or I'm not doing it properly).
I've tested a case in isolation and was able to narrow the problem down. Here are the steps.
Get the entity from the context and dispose the context. This detaches the entity.
public async Task<ArchiveEntry> GetEntry(string api, string id)
{
// Omitted some code for simplicity
ArchiveEntry entry;
using (var db = new TwinTailDb())
{
entry = await db.ArchiveEntries.Where(a => a.Id == id).SingleOrDefaultAsync();
}
return entry;
}
Save the entity regardless if it was changed or not.
public async Task Save()
{
// Omitted some code for simplicity
using (var db = new TwinTailDb())
{
db.ArchiveEntries.AddOrUpdate(a => a.Id, this);
await db.SaveChangesAsync();
}
}
Finally, reattach the detached entity.
public async Task RevertChanges()
{
using (var db = new TwinTailDb())
{
if (db.Entry(this).State == EntityState.Detached && Id != 0)
{
db.ArchiveEntries.Attach(this);
//await db.Entry(this).ReloadAsync(); // Commented since this is irrelevant
}
}
}
Then I run this function to use the code above.
public async Task Test()
{
ArchiveEntry entry = await ArchiveService.GetEntry(null, "7");
await entry.Save();
await entry.RevertChanges();
}
Then it throws this error:
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.
Here's an important point. If I skip step 2, it doesn't throw an exception.
My speculation is that the entity was modified and saved in a different context. If step 2 was skipped, the entity remains unchanged so reattaching it doesn't pose a problem (just guessing). However, this entity is already in a detached state so this should be irrelevant.
Another point, the ChangeTracker doesn't contain anything during these steps. Also, if I perform any context operation on the detached entity, it throws an exception saying that it should be attached first. I've also noticed that the internal _entitywrapper still has a reference to the old context.
So, finally, here's the question. How do I properly reattach an entity and why does this exception happen.
I've asked something similar in a different question (How to revert changes on detached entities) but felt that I need to post a new one since this is more general.
The architecture I'm using in my program is that the data access layer is abstracted from the application.
It looks like you are implementing these methods on the ArchiveEntry class itself. That is not an abstracted data access layer, and passing entities around to many short-lived contexts like this will get you into trouble.
Instead of giving the entity classes their own methods for managing persistence concerns, you should put that code into a class separate from the entity, and make sure that once an entity gets attached to a context, you keep using that same context until it gets disposed. Once the context is disposed, if you want to do another operation on the entity, you should retrieve it from a new context before trying to do any (attached) things with it.
I was using AsNoTracking() so, the exception was odd to me. The code below fixed the issue in my code. I think I could get ride of the code in the try block and only use the catch block, but I never tested the concept.
Entity Base class with PrimaryKey
public class EntityBase<K>
{
[NotMapped]
public virtual K PrimaryKey {get;}
}
Update function
public static void Update<T,K>(DbContext context, T t) where T:EntityBase<K>
{
DbSet<T> set = context.Set<T>();
if(set==null) return;
if(context.Entry<T>(t).State == EntityState.Detached)
{
try
{
T attached = set.Attached(t);
context.Entry<T>(attached).State = EntityState.Modified;
}catch(Exception ex)
{
T found = set.Find(t.PrimaryKey);
context.Entry(found).CurrentValues.SetValues(t);
}
}
context.SaveChanges();
}
Extension Function
public static void Update<T,K>(this DbContext context, T t) where T:EntityBase<K> => Update<T,K>(context, t);
So I'm new to EF (I'm using EF6) and I have problem understanding the concept, I'm trying to update entity with child collection.
Here's my entity class :
public class TimeSheet
{
public int TimeSheetID { get; set; }
public virtual ICollection<TimeSheetDetail> Details { get; set; }
}
public class TimeSheetDetail
{
public int TimeSheetDetailID { get; set; }
public int TimeSheetID { get; set; }
public virtual TimeSheet TimeSheet { get; set; }
}
My update method :
public void Update(TimeSheet obj)
{
var objFromDB = Get(obj.TimeSheetID);
var deletedDetails = objFromDB.Details.Except(obj.Details).ToList();
_dbContext.Entry(obj).State = EntityState.Modified;
//track if details exist
foreach (var details in obj.Details)
{
_dbContext.Entry(details).State = details.TimeSheetDetailID == 0 ? EntityState.Added : EntityState.Modified;
}
//track deleted item
foreach (var deleted in deletedDetails)
{
_dbContext.Entry(deleted).State = EntityState.Deleted;
}
}
public TimeSheet Get(object id)
{
//return _timeSheet.Find(id); //Without AsNoTracking I got error
int x = Convert.ToInt32(id);
return _timeSheet.AsNoTracking().SingleOrDefault(a => a.TimeSheetID == x);
}
Above code give me Attaching an entity of type 'ClassName' failed because another entity of the same type already has the same primary key value. So my quesstion is :
How do you update child collection with EF? Means that I need to Add new if it doesn't exist in DB, update otherwise, or delete from DB if it is removed in the POST.
If I don't use AsNoTracking(), it will throw Saving or accepting changes failed because more than one entity of type 'ClassName' have the same primary key value. I notice that the error was cause by my DbSet add the data from DB to its Local property if I don't use AsNoTracking() which cause the EF framework to throw the error because it thinks I have a duplicate data. How does this work actually?
As you can see I'm trying to compare objFromDb against obj to check if user remove one of the details so I can remove it from the database. Instead I got bunch of DynamicProxies from the collection result. What is DynamicProxies and how does it work?
Is there any good article or 101 tutorial on EF? So far I've only see a simple one which doesn't help my case and I've looking around and find a mixed answer how to do stuff. To be honest, at this point I wish I would just go with classic ADO.Net instead of EF.
For a better understanding of the entity framework, think of the DbContext as proxy between your application and the database.
The DbContext will cache everything and will use every bit of data from the cached values unless you tell it not to do so.
For 1.: This depends on your environment if your DbContext is not disposed between selecting and updating the entites you can simply just call SaveChanges and your data will be saved. If your DbContext is disposed you can detach the entites from the context, change the data, reattach them and set the EntityState to modified.
I can't give you a 100% sure answer, because I stopped using the entity framework about half a year ago. But I know it is a pain to update complex relations.
For 2.: The command AsNoTracking tells the EF not to track the changes made to the entities inside this query. For example your select 5 TimeSheets from your Database, change some values in the first entity and delete the last one. The DbContext knows that the first entity is changed and the last one is deleted, if you call SaveChanges the DbContext will automatically update the first entity , delete the last one and leave the other ones untouched. Now you try to update an entity by yourself and attach the first entity again to the DbContext.
The DbContext will now have two entites with the same key and this leads to your exception.
For 3.: The DynamicProxies is the object that the entity framework uses to track the changes of these entities.
For 4.: Check this link, also there is a good book about entity framework 6 (Title: "Programming Entity Framework")