Assign a yet unassigned ID to a new entity - c#

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.

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

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

Entity Framework: how do I prevent an attached object from getting validation errors on properties not updated?

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

ObjectStateManager error while adding second data (Entity Framework)

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

Foreign key values without navigation properties

Is it possible to assign foreign key values manually when inserting records?
I do not want to use a TransactionScope or similar construct. But i do want to set the foreignkey value before calling SaveChanges()
For example:
EntityX x = new EntityX();
x.Name = "test";
ctx.AddToEntityX(x);
EntityY y = new EntityY();
y.Name = "Test";
y.EntityXID = x.ID; // <--- I want this. Not using a navigation property, but its 0.
ctx.AddToEntityY(y);
ctx.SaveChanges();
yes it is possible but a lot of trouble you have to assign it trough EntityReference :
y.EntityXReference.EntityKey = new EntityKey("Enitites.YSet", "Id", x.id);
see EntityKey Constructor
for details of parameters
for other reference see Tip 7 - How to fake Foreign Key Properties in .NET 3.5 SP1
I don't see any problem with using navigation property in your example. Also there is no need for transaction scope because SaveChanges uses transaction internally.
Theoretically if you delete all associations in your conceptual model (EDMX designer) and manually delete all associations in SSDL part of EDMX file and then map FKs to new scalar properties you should be able to do that. But you will degrade EF so much that you should even not use it and revert back to ADO.NET or Linq-to-sql. Moreover once you touch SSDL part of EDMX you can't use Update from database anymore.
If you create a new entity it won't have an ID until it get's persisted. Then you would have to retrieve it from the DB and get the idea. Using navigation properties is definitely your best choice in this example. So instead of:
y.EntityXID = x.ID;
you would use
y.EntityX = x;

Categories