I have a library that processes data from a database using Entity Framework Core 7. It returns entities to the client code, which puts them into navigation properties like this:
var newEntity = new SomeEntity {
SomeProperty = "blah blah blah",
SomeNavProperty = theLibrary.GetEntity()
};
theLibrary.Process(newEntity); // Calls DbSet<TEntity>.Add
theLibrary.Save(); // Calls DbContext.SaveChanges
Everything works fine, but the sample method GetEntity
returns a tracked entity, thus if the client code changes its properties, the changes will be persisted to the database as soon as SaveChanges
is called, and I don't want such behavior. I tried returning a non-tracked entity from GetEntity
:
public SomeAnotherEntity GetEntity() {
return dbContext.SomeAnotherEntities.AsNoTracking().SingleOrDefault(e => e.Id == 1);
}
This resulted in an SqlException
thrown by the SaveChanges
method of the DbContext
:
Cannot insert explicit value for identity column in table 'SomeAnotherEntities' when IDENTITY_INSERT is set to OFF.
So, how do I use non-tracked entities (from AsNoTracking
) in navigation properties? In other words, how do I fix this exception? Attaching the non-tracked related entity before calling DbSet<TEntity>.Add
didn't work.
Code from the library
In fact, my class that works with database is generic. TEntity
is the type of entity it works with. The Process
method code:
public void Process(TEntity entity)
{
int? id = (int?)typeof(TEntity).GetProperty("Id")?.GetValue(entity);
if (id == null)
{
throw new InvalidOperationException("No Id property");
}
if (id == 0) table.Add(entity);
else
{
var tracked = table.Find(id);
if (tracked == null) return;
foreach (var property in tracked.GetType().GetProperties())
{
if (property.Name == "Id") continue;
property.SetValue(tracked, property.GetValue(entity));
}
}
}
table
is the DbSet<TEntity>
(already fetched in the constructor) where entities of type TEntity
are stored. The Save
method just calls SaveChanges
.
SomeEntity
definition, if needed to answer:
public class SomeEntity {
public int Id { get; set; }
public string SomeProperty { get; set; }
public SomeAnotherEntity SomeNavProperty { get; set; }
}
What I tried
In the Similar Questions block, there were two questions about the same issue as mine.
Using no tracking navigation property EF core has one answer, which didn't work for me.
Do not track changes on navigation property in EF has no answers, no solution in comments and seems to be non-answerable till the author adds their code.