Reattaching a detached entity throws an exception - c#

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

Related

Why do I get tracking error while updating entity in EF Core? [duplicate]

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.

Set Entity State to Modified for entities with different reference

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

Confuse about tracking in EF (updating entity with child collection)

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

Unable to update modified entities

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

EF keeps trying to persist invalid object

I'm having a rather strange issue with Entity Framework 4.3 in my MVC application. I'm using a Unit of Work wrapper around DbContext, and in my MVC application I use Unity to pass this UOW to my repositories, and repositories to controllers. I've registered the UOW type with the HierarchicalLifetimeManager.
When I try to persist an entity to the database that raises an error, e.g. the database throws a UNIQUE constraint violation, the entity is kept inside EF's ObjectStateManager. So when I go back in my application to fix the error and save the new entity (without errors), EF first tries to add the old and invalid object again, thus failing with the same error.
What am I missing here? I believe that the invalid object should be completely forgotten by EF and that this would be done automatically. But it's clearly not the case.
To add objects to DbContext in order to persis them, the following command gets called (where base is the DbContext):
base.Set<TEntity>().Add(objectToPersist);
And to commit the changes to the database, I call:
base.SaveChanges();
Which throws the error.
I believe that the invalid object should be completely forgotten by EF
and that this would be done automatically. But it's clearly not the
case.
Right, that's not the case and I've never heard that entities would be detached from the context automatically when an exception occured.
There are basically two options to deal with the problem. I show a simple model with your example of a unique key constraint violation:
public class Customer
{
// so we need to supply unique keys manually
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
using (var ctx = new MyContext())
{
var customer = new Customer { Id = 1, Name = "X" };
ctx.Customers.Add(customer);
ctx.SaveChanges();
}
// Now customer 1 is in database
using (var ctx = new MyContext())
{
var customer = new Customer { Id = 1, Name = "Y" };
ctx.Customers.Add(customer);
try
{
ctx.SaveChanges();
// will throw an exception because customer 1 is already in DB
}
catch (DbUpdateException e)
{
// customer is still attached to context and we only
// need to correct the key of this object
customer.Id = 2;
ctx.SaveChanges();
// no exception
}
}
}
}
The above is the prefered solution: Correct the object which is attached to the context.
If you need - for whatever reason - to create a new object you must detach the old object from the context. That object is still in state Added and EF will try to save the object again when you call SaveChanges leading to the same exception as before.
Detaching the old object would look like this:
try
{
ctx.SaveChanges();
// will throw an exception because customer 1 is already in DB
}
catch (DbUpdateException e)
{
ctx.Entry(customer).State = EntityState.Detached;
// customer is now detached from context and
// won't be saved anymore with the next SaveChanges
// create new object adn attach this to the context
var customer2 = new Customer { Id = 2, Name = "Y" };
ctx.Customers.Add(customer2);
ctx.SaveChanges();
// no exception
}
This procedure can be tricky if relationships are involved. For example if customer has a relationship to a list of orders, detaching the customer object will delete references between customer and its orders if the orders are attached to the context as well. You have to reestablish the relationships with the new customer2.
Therefore I'd prefer to modify the attached object to put it into correct state. Or let the application crash because such constraint violations usually indicate bugs in the code or - in a multiuser environment - should be handled with proper optimistic concurrency checks.
looks like you'll have to tell EF that you changed your mind about the invalid object:
base.Set().Remove(objectToPersist);
If you want to reset the changes, you could set the ObjectContext to null and re-instantiate it.

Categories