When I adding more than one consecutive data an error occurred in SaveChanges() method.
EXCEPTION
The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: AcceptChanges cannot continue because the object's key values conflict with another object in the ObjectStateManager. Make sure that the key values are unique before calling AcceptChanges.
My baseservice
public void Delete(T entity)
{
ObjectSet.DeleteObject(entity);
Context.SaveChanges();
}
public void Add(T entity)
{
ObjectSet.AddObject(entity);
Context.SaveChanges();
}
public void Attach(T entity)
{
ObjectSet.Attach(entity);
Context.SaveChanges();
}
public void Update(Expression<Func<T, bool>> where, T entity)
{
var ent = First(where);
ent = entity;
Context.SaveChanges();
}
I had this problem and found out that I was doing the following operations, making EntityFramework to become not in sync with the data in the database:
1) Make a query on the rows of a table through Entity Framework's Context. Doing so, EntityFramework context preserves a copy of those objects in its Local view.
2) Truncate the table through an SQL Query (so the Entity Framework context has not idea this happened. The entities are still in its local view even if they were truncated in the database).
Since the primary key of the table is auto incrementing (IDENTITY (1,1)), the truncation call makes the primary key counter of the table to reset to 1.
3) Add rows to the table through Entity Framework, and then call SaveChanges(). Because of the table truncation, the primary key of the new row is 1. After creating the row, EntityFramework queries for the database for the row values, creates a new Entity, populates the values in the Entity and adds the Entity to its local view.
Because the context already had another object with primary key = 1 stored in its local view (from Step 1), an exception is thrown as it tries to add a second Entity with the same primary key to the local view.
To avoid this situation, Entity Framework must be remain in sync with the database content before making new operations.
In my case, I had to fix this by calling:
Context.MyTableEntities.Local.Clear();
Context.SaveChanges();
So the Entities were deleted first, and the context was told about it. Then I truncated the table with an SQL Query to reset the auto increment counter.
Since the objects were deleted from the local view first, and the table truncation done afterwards (so it resets the auto increment counter), primary key conflicts were avoided.
Hope that helps.
Are you certain you're adding different entities to EF? The inner exception states that AcceptChanges() is failing because the current entity that you're trying to add shares a key with an entity that's already being tracked.
For more on AcceptChanges(), take a look at: http://msdn.microsoft.com/en-us/library/system.data.objects.objectstateentry.acceptchanges.aspx
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 using Entity Framework Core together with the repository pattern. To help me out, I coded one base repository with the basic CRUD methods. The update method is as follows:
public void Update(TEntity entity)
{
var contextEntry = _context.Entry<TEntity>(entity);
if (contextEntry.State == EntityState.Dettached)
{
_context.Attach(entity);
}
contextEntry.State = EntityState.Modified;
_context.SaveChanges();
}
Given the BaseRepository class containing this method, I created one User repository inheriting from this
public class UserRepository : BaseRepository<User>, IUserRepository
{
}
And I've used this in the PUT method of one Web API coded with ASP.NET Core
[HttpPut("~/api/users/{id}")]
public IActionResult Put(int id, [FromBody] User user)
{
if (user == null || user.UserId != id)
{
return BadRequest();
}
userRepository.Update(user);
return new NoContentResult();
}
Now, when issuing a request, I get one error in the _context.Attach(entity) line. The exception says that it can't add the entity for tracking because there is already another entity with the same key being tracked.
When debugging I saw that contextEntry.State was set to Unchanged. Hence, it is obviously not equal to EntityState.Dettached. Still, the execution got inside the if statement and tried to attach the entity.
Something is quite wrong here. Is this a bug? Or am I doing something very wrong? I believe that I'm the one doing something very wrong with this update strategy, but I'm unsure about it. In that case, what is wrong with my approach?
EDIT: I updated the Update method to use just _context.Update(entity) and after _context.SaveChanges(). Still, the _context.Update(entity) throws one InvalidOperationException with this message:
Additional information: The instance of entity type 'User' 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.
You are getting same entity from database some where in the project that's why it give you error.
In Update method you just add the entity in the context that's why you get contextEntry.State Unchanged.
You can fix this problem in two ways.
You need to call the Detach method on same entity when you get it from database.
copy the values from entity which you received in Update method to the existing context entity and save that entity in database.
All the information is in the exception message... you already have another copy of the entity with that primary key attached.
I would recommend one of the following (preferred first):
Use a new Context for each action, don't have a long-lived repository/context
Use .Set<TEntity>.Find(object[] key) on your context using the Primary Key, in order to retrieve any entity you already have.
In your current update method, use the Set<TEntity>.Local.Find(..) to check if it already exists
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've got the below model.
I am trying to delete a given productType associated with an ActivityType; But when I try to instigate a delete via my generic methods, its not only deleting the relationship but its trying to delete the ProductType as well!! with a primary key of Zero in the where clause causing the erorr
"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries."
code to delete below;
public void DeleteEntityAndSaveChanges<T, T1>(T entity, List<T1> subEntities)
where T : class
where T1 : class
{
Set<T>().Attach(entity);
DeleteEntitiesAndSaveChanges(subEntities);
}
public void DeleteEntitiesAndSaveChanges<T>(List<T> entities) where T : class
{
foreach (var entity in entities)
{
Set<T>().Attach(entity);
Set<T>().Remove(entity);
}
SaveChanges();
}
Usage:
DbContext.DeleteEntityAndSaveChanges(request.ActivityType, request.ActivityType.ProductTypes);
This is the sql thats being generated:
exec sp_executesql N'delete [dbo].[ProductTypeActivityTypes]
where (([ProductType_ProductTypeId] = #0) and ([ActivityType_EntityObjectId] = #1))',N'#0 bigint,#1 bigint',#0=1,#1=20
This is the offending SQL that dont want sent out, but is being generated by EF:
exec sp_executesql N'delete [dbo].[ProductTypes]
where (([ProductTypeId] = #0) and [TimeStamp] is null)',N'#0 bigint',#0=1
any ideas as to how I can get it to only delete the relationship?
Cheers.
When you call Remove on the DbSet, this means delete that entity from the database when SaveChanges is called. As I understand it, what you really want to do is remove the association between the two entities. In general you do this by removing one entity from the collection navigation property of the other entity. For example:
product.Activities.Remove(activity);
But there is a wrinkle here. In your sample code you are calling Attach to attach both types of entities to the context. Calling Attach will not setup or restore any relationships between the entities. This is because, for many-to-many relationships, there is no FK in the entity to provide information about how it is related to other entities. The FKs are handled by the join table, which is not exposed in an EF many-to-many mapping.
There are a couple of approaches to dealing with this. First, if possible and sensible for your architecture, you can let the EF context track the entities from the time they are queried until the time you call SaveChanges. This way EF will keep track of the relationships for you, including keeping track of deletes.
Second, if the entities need to be attached to a new context, then you will need to keep track of the relationships such that those can be restored as well. There are a few ways to restore the relationships. One way is to build the graph of related entities before calling Attach. EF will then traverse and attach the entire graph, including relationships, when Attach is called. For example:
// Restore the graph
product.Activities.Add(activity1);
product.Activities.Add(activity2);
context.Products.Attach(product);
// Delete the relationship
product.Activities.Remove(activity1);
context.SaveChanges();
(I'm not using the generic methods you have just to make the code a bit clearer. It should work the same way with generics.)