How do I use non-tracked entities in navigation properties in EF Core 7?

767 Views Asked by At

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.

2

There are 2 best solutions below

1
Eugene On

You can use foreign key property instead of navigation property while you creating entity

var entityId = theLibrary.GetEntity().Id;
var newEntity = new SomeEntity {
  SomeNavPropertyId = entityId,
  ...
};

It's simple and guarantees the expected behavior of non-tracked entity that the FK refers to

Is it acceptable in your case?

4
AudioBubble On

The error is not at all related to the AsNoTracking() in EF core. Error is coming when you are trying to insert data into the database.

The reason is Id column in the table, is Identity Column, which means, the Id is automatically inserted by EF.

Ways to Solve this error:

  1. You can set Identity Insert to OFF in your SQL table. Identity Insert On/Off

  2. If you are passing id value, when Inserting the data into the database, i.e form Json. Just remove that value, as EF code will Automatically insert it.

Ex: If your json is as below:

{
  "id" : "1",
  "name" : "TestName"
}

then change it to :

    {
      "name" : "TestName"
    }

That should work.