Removing range using EF Core - c#

I´m getting an error when using RemoveRange to bulk delete data, in my unit tests, using InMemoryDatabase.
Here is the code:
public void DeletePatient(Paciente patient)
{
var schedules = dbContext.Schedules.AsNoTracking().Where(x => x.PatientId == patient.Id).ToList();
dbContext.Schedules.RemoveRange(schedules);
dbContext.Patients.Remove(patient);
}
This throws this error:
InvalidOperationException: The instance of entity type 'Schedule' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.
But, if I perform a foreach and reload each entity, it works:
foreach(var item in schedules)
{
var h = dbContext.Schedules.Find(item.Id);
dbContext.Remove(h);
}
The same foreach, using the item directly gives same error:
foreach(var item in schedules)
{
dbContext.Remove(item);
}

Try removing the AsNoTracking clause. I haven't tested it but my guess is this is causing EF to re-read the entities from the database and not finding the ones already in the context. Without the clause it should find the actual entities in the context to be removed.

Related

EF Core duplicate keys: The instance of entity type '' cannot be tracked because another instance with the key value '' is already being tracked

I'm working on a form using EF Core in Blazor Server. I had a number of issues with entity tracking so I set all of my queries to AsNoTracking and designed my service to create a new instance of dbcontext for each query. I think this is appropriate as none of the returned values will be edited - only the form data that users enter and the id references to the queried fields, such as employee numbers, will be stored. For inserting data, I use this:
using var context = Factory.CreateDbContext();
context.SetupForm.Attach(model);
context.Entry(model).State = EntityState.Added;
await context.SaveChangesAsync();
I am attaching the data rather than adding it and then setting the form object state to added. This ensures EF Core doesn't attempt to insert the existing employee objects when it inserts the form data.
The trouble starts in a section of the form that can have as many items as the user wants. The select a couple of employees and type in relevant data. When they submit the form, they may have selected the same employee in multiple items. As these employees were selected from separate contexts, they are two separate instances with the same ID. Of course, EF Core doesn't like this and throws errors like this one:
The instance of entity type 'Principal' cannot be tracked because another instance with the key value '{EmployeeID: 1234}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
I understand why this error is occurring but I need to be able to attach multiple entities in this way. How can I work around this issue?
One thing I could do is assign the foreign keys manually but that would be rigid and require updates whenever the model changes.
just try this
using var context = Factory.CreateDbContext();
context.Set<Principal>().Add(model);
//or maybe context.Principals.Add(model);
await context.SaveChangesAsync();
This seems to do the trick! What it does is mark any entity that lacks a key as added. Otherwise, the entity is ignored entirely.
using var context = Factory.CreateDbContext();
context.ChangeTracker.TrackGraph(model, node =>
{
if (!node.Entry.IsKeySet)
{
node.Entry.State = EntityState.Added;
}
});
await context.SaveChangesAsync();
None of the items that have a key will need to be inserted. Treating them as untracked then solves any issues with duplicates and only inserts the rows that need it.
More information: https://learn.microsoft.com/en-us/ef/core/change-tracking/identity-resolution#resolve-duplicates

Why is RemoveRange failing on entities already being tracked?

After adding an entry using entities collection of DB context, if I try to call RemoveRange I get an InvalidOperationException.
If I use another instance of the DB context to call RemoveRange, then it works fine. But when I use the same DB context, it fails.
var ingredient = new Ingredient()
{
Id = 1,
Name = "Pepper",
};
await context.Ingredients.AddAsync(ingredient);
await context.SaveChangesAsync();
context.Ingredients.RemoveRange(context.Ingredients);
await context.SaveChangesAsync();
The error message is :
System.InvalidOperationException: The instance of entity type 'Ingredient' cannot be tracked because another instance with the key value '{IngredientId: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
If I understood correctly, Remove() and RemoveRange() would attach the entity if it were not already tracked. In my case, an instance is already being tracked... true... but then,
what would be the correct way to remove all entries, without caring which entities are already tracked and which are not yet tracked ?
Any clue ?
Try to replace the line context.Ingredients.RemoveRange(context.Ingredients); by the following:
var tempIngredients = context.Ingredients.ToList();
context.Ingredients.RemoveRange(tempIngredients);

Entity Framework inserting entity twice in two different contexts

I'm using Entity Framework Core to store an object graph in a database. At various times while I'm building the graph, I create an entity, store it to the database, and the release the context. However, I'm running into an issue where EFC is trying to insert an entity that has already been inserted when it is connected to a new entity. This is best explained with code. Here's a short repro piece of code (this is straight line code, but the two uses of contexts happen at different times and places in the code).
In the second call to context.SaveChanges(), I get the following exception:
SqlException:
Cannot insert explicit value for identity column in table 'Namespace' when IDENTITY_INSERT is set to OFF.
When I look at the SQL being executed, it is trying to insert the namespace entity again, presumably because myType is being saved to the DB and it has a reference to the dbNamespace entity.
// see if namespace is in the db and add it if not
string someNamespaceString = "foobar";
CodeDatabase.Models.Namespace dbNamespace;
using (var context = new CodeFactsContext())
{
dbNamespace = context.Namespace.FirstOrDefault(ns => ns.Namespace1 == someNamespaceString);
if (dbNamespace == null)
{
dbNamespace = new Namespace() { Namespace1 = someNamespaceString };
context.Namespace.Add(dbNamespace);
}
context.SaveChanges();
}
// Type entity created somewhere from the code
var myType = new CodeDatabase.Models.Type()
{
FullName = "foobar.mytype",
ShortName = "mytype",
Namespace = dbNamespace // this is already in the DB
};
// check if myType is in the db and add it if not
using (var context = new CodeFactsContext())
{
var dbType = context.Type.FirstOrDefault(t => t.FullName == myType.FullName);
if (dbType == null)
{
dbType = myType;
context.Add(dbType);
}
context.SaveChanges(); // throws exception
}
Any idea how to get EF Core to recognize (in the second context.SaveChanges()) that myType should be inserted into the database, but myType.Namespace should not because it's already there? Both of the entities have an int id that is autogenerated by the DB and the id of Namespace is set to the database value after the first call to SaveChanges. I thought EF Core would recognize that the id is not 0 and not try to save it. Any help/suggestions very welcomed.
I thought EFC would recognize that the id is not 0 and not try to save it.
The problem is that you are using Add method which marks all reachable and not tracked entities as new, regardless of the key value (this is to allow the identity insert scenarios). This is explained in the Disconnected Entities - Working with graphs - All new/all existing entities. While your screnario falls into Mix of new and existing entities.
Any idea how to get EFC to recognize (in the second context.SaveChanges) that myType should be inserted into the database, but myType.Namespace should not because it's already there? Both of the entities have an int id that is autogenerated by the DB and the id of Namespace is set to the database value after the first call to SaveChanges.
Actually there is a simple solution explained in the second documentation link:
With auto-generated keys, Update can again be used for both inserts and updates, even if the graph contains a mix of entities that require inserting and those that require updating
where "again" refers to Saving single entities:
The Update method normally marks the entity for update, not insert. However, if the entity has a auto-generated key, and no key value has been set, then the entity is instead automatically marked for insert.
Luckily your entities use auto-generated keys, so simply use Update instead of Add:
if (dbType == null)
{
dbType = myType;
context.Update(dbType); // <--
}

Bulk insert data entity framework 7

Normally In my previous project, I can do bulk insert by passing a list of object as code below
public void Create(List<ApplicationUserRole> item)
{
foreach (var data in item)
{
_dbContext.ApplicationUserRole.Add(data);
}
_dbContext.SaveChanges();
}
But for now i keep hitting error
InvalidOperationException: The instance of entity type
'Docdoc.Models.ApplicationUserRole' cannot be tracked because another
instance of this type with the same key is already being tracked. For
new entities consider using an IIdentityGenerator to generate unique
key values.
I need change my code at below to work
foreach (var data in item)
{
_dbContext.ApplicationUserRole.Add(data);
_dbContext.SaveChanges();
}
I know It is very bad practice. The performance will be very slow by insert large amount of data
Any solution for this problem?
The exception message you saw may not be fixed by calling "SaveChanges" after each Add. The root cause of your problem is that your instance of DbContext already has a ApplicationUserRole entity with the same key (guessing it is ApplicationUserRole.Id or something). This error is common and is often caused by manually setting temporary key values, e.g. setting ApplicationUserRole.Id to -1. (See https://github.com/aspnet/EntityFramework/issues/4488 for example.)
If the error is not being caused by incorrectly setting temp key values, then also make sure your instance of DbContext is short-lived and only used in one thread. In other words, use DbContext for one operation only.
public void Create(List<ApplicationUserRole> item)
{
using (var context = new MyContext())
{
context.ApplicationUserRole.AddRange(data);
context.SaveChanges();
}
}

How do I update Entity Framework objects without changing ALL instances of that object?

I have this code which uses Entity Framework 4.1 to access database entities:
public string Test()
{
Navigation nav = db.Navigations.FirstOrDefault();
List<Navigation> lNav = db.Navigations.ToList();
foreach (var item in lNav)
{
item.Label += " [Edited]";
}
return nav.Label;
}
When I run this in asp.net mvc it returns this:
News [Edited]
I expected it to return:
News
Because I thought my foreach would only modify the contents of lNav. Instead, it seems to modify all instances of the entity objects.
How can I modify lNav without also modifying nav?
Try AsNoTracking():
Navigation nav = db.Navigations.AsNoTracking().FirstOrDefault();
List<Navigation> lNav = db.Navigations.AsNoTracking().ToList();
This way loaded entities are not attached to the context and two different instances should be created for the first entity. By using AsNoTracking you disable the identity mapping between key values of an entity and object references which is responsible for the behaviour you observed. (There can always only be one entity reference with a given key in the context and EF does not create a new object if you load an entity with the same key. Instead it returns the object which already exists in the context.)
Be aware that you cannot use this if you intent to update entities with the help of EFs change tracking mechanism. AsNoTracking() is designed for readonly scenarios.

Categories