0

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.

SNBS
  • 671
  • 2
  • 22
  • Post the actual full exception text, not just the message part. LINQ queries don't generate INSERTs. Somehow, somewhere else your code is adding objects in the `Added` state. Those objects may have come from that query but EF or any other code doesn't know that. It only knows you attached that object in the `Added` state. – Panagiotis Kanavos May 31 '23 at 12:12
  • @PanagiotisKanavos I just told you I'm not attaching entities in `Added` state! – SNBS May 31 '23 at 12:14
  • The facts disagree. If you attach *any* object, any related objects are also attached. If you use `Update` the actual state is controlled by the value of the PK/ID. If it's default, the object is attached in the Added state – Panagiotis Kanavos May 31 '23 at 12:14
  • @PanagiotisKanavos Hmm OK, let me download my code and post it, and you see – SNBS May 31 '23 at 12:15
  • To fix your problem you need to understand what your code is doing. And post the actual full exception text. That will show you *and us) where the error occurred and which calls led to it – Panagiotis Kanavos May 31 '23 at 12:16
  • And the entities I fetch using `AsNoTracking` — they're not in `Added` state, eh? – SNBS May 31 '23 at 12:16
  • If you don't want to persist a property, mark it as `NotIncluded`. AsNoTracking doesn't have any effect on the object itself. – Panagiotis Kanavos May 31 '23 at 12:16
  • Why `NotIncluded`, I want the user to access all property. But not modify them. Or I don't understand? – SNBS May 31 '23 at 12:17
  • `hey're not in Added state, eh` wrong - you're creating a new `SomeEntity` object so when *that* object is attached to the DbContext, all related objects, including the one stored in `SomeNavProperty` will also be attached and persisted. – Panagiotis Kanavos May 31 '23 at 12:19
  • `access all property. But not modify them` what are you trying to do? Are you saving that object before returning it to the client? In that case save it *before* setting the property you don't want to save. – Panagiotis Kanavos May 31 '23 at 12:20
  • @PanagiotisKanavos Posted my code. – SNBS May 31 '23 at 12:23
  • And "you're creating a new SomeEntity object so when that object is attached to the DbContext, all related objects, including the one stored in SomeNavProperty will also be attached and persisted." — no, I said in the question that I tried attaching the related entities (fetched using `AsNoTracking`). I set them to the `Unchanged` state, and the problem stayed. – SNBS May 31 '23 at 12:25
  • The code shows you do call Add. So did your initial comments in the code. That means all objects are added in the `Added` state. If you thought that `AsNoTracking()` would produce un-attachable objects, you were wrong. You can easily set the property *after* saving though – Panagiotis Kanavos May 31 '23 at 12:25
  • `I set them to the Unchanged state, and the problem stayed.` not in the code you posted – Panagiotis Kanavos May 31 '23 at 12:26
  • But I tried it! – SNBS May 31 '23 at 12:26
  • Set the property *after* saving. Your assumptions about what `AsNoTracking()` does are wrong. – Panagiotis Kanavos May 31 '23 at 12:27
  • "The code shows you do call Add." — I'm adding the new entity, not those fetched from `AsNoTracking`. Or you mean they are set to `Added` state, too? – SNBS May 31 '23 at 12:27
  • Should I first add and save the new entity without setting navigation property, then set it, modify entity and save again? – SNBS May 31 '23 at 12:29

2 Answers2

0

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?

Eugene
  • 169
  • 1
  • 6
  • No, I can't use a foreign key because I need easy access to related entities for the client code (e.g. `theLibrary.GetEntity().SomeNavProperty.BlahBlahBlah`). – SNBS May 31 '23 at 11:53
0

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.

Ajay Managaon
  • 450
  • 2
  • 9
  • As you can see in the question, I don't set `Id` when initializing the `SomeEntity` instance. It is set to default value (i.e. zero) and thus automatically generated by the database. (Not by EF, by the way.) What about "You can set Identity Insert to OFF in your SQL table." — the exception message clearly says that IDENTITY_INSERT is already set to OFF! – SNBS May 31 '23 at 11:57
  • @PanagiotisKanavos "You explicitly attached those objects in the `Added` state." — wrong. I'm not adding entities *fetched* from the database. I'm just *linking* them to a completely new entity and then adding it. – SNBS May 31 '23 at 12:11
  • @SNBS queries don't generate INSERTs. The exception is coming from somewhere else and telling you you added that object. `AsNoTracking()` means EF won't track those objects for changes, it doesn't mean they won't be inserted if you explicitly call `Add` or `Update` on them, or on a root – Panagiotis Kanavos May 31 '23 at 12:13
  • @SNBS PS post the entire error and the saving code. That's where the problem is – Panagiotis Kanavos May 31 '23 at 12:13