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); // <--
}
Related
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
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.
I am developing an Web application using C#/MVC and EF6. I am using the Database First approach, as I am attaching to an existing database (SQL Server 2008-R2).
I have one table that contains 2 foreign keys to the same target as follows:
Table Artifact:
int ArtifactId;
int AnalystId; //Employee performing analysis work on artifact
int ChampionId; //Employee servind as champion for artifact
And the target table is very straightforward.
Table Employee:
int EmployeeId;
// Employee info
I managing the Datbase access as a disconnected repository so that when I retrieve and update the information, I can manage the state.
public Candidate GetArtifact(int artifactId)
{
using (var context = new DataEntities())
{
return context.Artifacts.AsNoTracking()
.Where(x => x.ArtifactId == artifactId)
.Include(x => x.Employee) //Analyst
.Include(x => x.Employee1) //Champion
.FirstOrDefault();
}
}
public int SaveArtifact(Artifact artifact)
{
using (var context = new DataEntities())
{
if (artifact.ArtifactId > 0)
{
context.Entry(artifact).State = EntityState.Modified;
}
else
{
context.Artifacts.Add(artifact);
}
context.SaveChanges();
return artifact.CandidateId;
}
}
Everything works as I would expect, except for the case where both the Analyst and the Champion reference the same record from the Employee record. In testing the update existing code path, I get one of 2 exceptions, depending on the initial state of the data. Note, the exception only happens when the data is updated, it retrieves correctly without issue.
When I attempt to update an artifact with both the Analyst and Champion referencing the same employee record. I get the following exception:
Attaching an entity of type 'Data.DataModel.Employee' 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.
Any suggestions on what I can do to correct this?
After doing a little additional digging, I found the solution to my particular problem. The pattern that I was following (established by one of my colleagues) was to eager load all data records. So the Fetch for the artifact included two Include statements to include the 2 different keys, as seen above in my original example.
Where this caused a problem is that, in the case where both the Champion and Analyst were referencing the same Employee record, the update saw the primary key/record from the Analyst already attached to context, so threw the exception when it attempted to attach what was actually the same record to the context again for the Champion.
My solution was to remove the eager loading from the retrieval, which for my particular design imposed no real issues since the Employee record is really referential only and never updated in conjunction with the artifact update.
so my GetArtifact method looks like the following after the fix:
public Candidate GetArtifact(int artifactId)
{
using (var context = new DataEntities())
{
return context.Artifacts.AsNoTracking()
.Where(x => x.ArtifactId == artifactId)
.FirstOrDefault();
}
}
And the couple of places that actually reference the Employee record just use the appropriate ID and directly fetches the record. This prevents the issue.
For me, the lesson learned was to understand Eager Loading and when to use it (and more importantly when NOT to use it).
We're trying to set up a shadow copy system for auditing some of the tables in our projects database. For Any change (Add, Update, Delete) a copy of that record get's saved to it's shadow table (we're using the term Version).
If we have a table called Profiles with columns (int)ProfileId, (varchar(50))Name, (date)UpdateDate, etc... we would have another table called ProfilesVersion with columns (int)ProfileVersionId, (int)ProfileId, (varchar(50))Name, (date)UpdateDate, etc...
I'm working on the system to make the copies. In the past I have used triggers in the database to catch Insert, Update, Delete. But now we're trying to do it using Entity Framework and Linq.
I can override the SaveChanges on DbContext, and get a second copy into the Version table. However, the key Id that get's populated on the first table does not end up in the Version table.
With Entity Framework, you can have two inserts to the database with data from one entity getting applied to the second. For instance:
var res = new Resource{
SomeValue = someParameter
};
_db.Resource.Add(res);
var newProfile = new Profile{
ProfileValue = anotherParameter,
ForeignResourceId = res.ResourceId // ResourceId is autogenerated
};
_db.Profile.Add(newProfile);
_db.SaveChanges();
var forResourceId = newProfile.ForeignResourceId;
Since Profile.ForeignResourceId and Resource.ResourceId are mapped in the model, the newProfile object has the ForeignResourceId that was assigned by the database after SaveChanges(). Somehow entity framework knows to put res.ResourceId into ForeignResourceId once it has been generated from the database.
My code which dynamically copies values from one entity into the Version table does not do that. It simply copies data from the first entity into the new record for the Version entity, but doesn't setup the relationship to populate the key field with the foreign key.
public int SaveChanges(Guid userId)
{
// ... some other code
// entityEntry is DbEntityEntry, the entity with changes we want to replicate
// Create audit entity.
DbSet set = this.Set(auditTypeInfo.AuditEntityType);
IAuditEntity auditEntity = set.Create() as IAuditEntity;
set.Add(auditEntity);
// Copy the properties.
DbEntityEntry auditEntityEntry = this.Entry(auditEntity);
foreach (string propertyName in auditTypeInfo.AuditProperties)
{
// This copies just the raw value, if any
auditEntityEntry.Property(propertyName).CurrentValue = entityEntry.Property(propertyName).CurrentValue;
}
// ...
return base.SaveChanges();
}
So, following with our example, if we add a Profile record, it get's it's ProfileId, but the ProfileVersion record does not.
How in the above code can I have entity framework set that value in the 'auditentity' that we are copying to?
If I understood you case correctly, then:
This will have to do with properties for your entity. If you entity has property (which, I suppose, is a key for you entity) has DatabaseGeneratedOption.Identity (assigned at OnModelCreating), which translates to IDENTITY (1,1) at sql level, there's nothing you can do, because all of that is being handled at database, not ORM level.
What you could do in this case, use IDENTITY_INSERT, which would allow you to assign Ids, but, it means that you would also have to generate Ids manually.
In short - get rid of automatic identity generation.
I am trying to attach an object that exists in the database to new DbContext, modify a single value (property: SubmissionGrpID, which is a foreign key) and then save the changes to the database. I am trying to do this without having to load the whole entity into the DbContext from the database.
I have already seen the question: How to update only one field using Entity Framework? but the answers did not help me.
The property: ID in the PnetProdFile table is the only column used in the primary key for that table. The code I am using is below:
using (SomeEntities db = new SomeEntities())
{
foreach (var id in FileIds)
{
PnetProdFile file = new PnetProdFile();
file.ID = id; // setting the primary key
file.SubmissionGrpID = 5; // setting a foreign key
db.PnetProdFiles.Attach(file);
var entry = db.Entry(file);
entry.Property(e => e.SubmissionGrpID).IsModified = true;
}
db.SaveChanges();
}
When I run the code, I get the following exception:
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
Then, after adding in the following line of code before I SaveChanges():
var errors = db.GetValidationErrors();
I get all the Not Null fields as required values for the PnetProdFile table in the Validation Errors. One way to prevent validation errors is to set
db.Configuration.ValidateOnSaveEnabled = false;
but then that prevents validation on the entire entity, even on properties that could have been updated.
So, to summarise, I am trying to update an object which already exists in the database without loading the related record from the database, and only get validation errors on properties that have been changed. How can this be done?
By default EF validates all Added and Modified entities. You can modify this behavior by overwriting the DbContext.ShouldValidateEntity method where you would return false for entities you don't want to be validated.
Attached rather than loaded/read Objects default to UnChanged
If you are using Rowversion/Timestamp optimistic locking then you must read the object first.
But if you have a vanilla object....
Context.Entry(poco).State = state; // tell ef what is going on.
Context.SaveChanges();
// Detached = 1,
// Unchanged = 2,
// Added = 4,
// Deleted = 8,
// Modified = 16,
There is also the option "AddOrdUpdate"
Context.Set<TPoco>().AddOrUpdate(poco);
Context.SaveChanges();
This option was originally intended for seed type scenarios.
People seem to be using it more in everyday cases.
I only use it for seeding. Do a quick search on pros and cons of AddOrdUpdate before
you use it in everyday production scenarios.
More on working with detached entities here