I have read several posts about this (e.g. Why can't EF handle two properties with same foreign key, but separate references/instances?) issue but none provided a satisfying solution.
Using EF6, inserting an object with two navigation properties fails, when the properties have the same type, represent the same entry (i.e. have the same primary key value) but are two different object instances.
Setup:
public abstract class EntityBase
{
public int Id { get; set; }
public bool IsTransient => Id <= 0;
}
public class Person : EntityBase
{
public string Name { get; set; }
}
public class Task : EntityBase
{
public int CreatorId { get; set; }
public int ReceiverId { get; set; }
public string Text { get; set; }
public Person Creator { get; set; }
public Person Receiver { get; set; }
}
When I try to insert a new Task like the following, an Exception occurs when creator
and receiver
are the same Person
(i.e. have the same Id
value). Exception is: "... because more than one entity of type 'Person' have the same primary key value.".
public void AddTask(string text, Person creator, Person receiver)
{
using (var context = new MyEntities())
{
Task task = new Task()
{
Text = text,
Creator = creator,
Receiver = receiver
};
context.Task.Add(task);
context.Entry(creator).State = EntityState.Unchanged;
context.Entry(receiver).State = EntityState.Unchanged; // Exception is raised here
context.SaveChanges();
}
}
The exception can be avoided using the following code:
public void AddTask(string text, Person creator, Person receiver)
{
using (var context = new MyEntities())
{
Task task = new Task()
{
Text = text,
Creator = creator,
Receiver = receiver
};
context.Task.Add(task);
context.Entry(creator).State = EntityState.Unchanged;
if (creator.Id == receiver.Id)
task.Receiver = creator;
else
context.Entry(receiver).State = EntityState.Unchanged;
context.SaveChanges();
}
}
Question: Is there any convenient way two avoid the exception except from checking the Id values of the navigation properties before insert?
This is a simple example given here. There are much more complex object graphs to be inserted where a manual check like shown here is quite cumbersome.
EDIT: The exception message is (german):
System.InvalidOperationException: Fehler beim Speichern oder Übernehmen der Änderungen, weil mehrere Entitäten des Typs 'Person' den gleichen Primärschlüsselwert aufweisen. Stellen Sie sicher, dass explizit festgelegte Primärschlüsselwerte eindeutig sind. Die von der Datenbank generierten Primärschlüssel müssen in der Datenbank und im Entity Framework-Modell ordnungsgemäß konfiguriert sein. Verwenden Sie den Entity Designer für die Database First-/Model First-Konfiguration. Verwenden Sie die Fluent-API 'HasDatabaseGeneratedOption' oder 'DatabaseGeneratedAttribute' für die Code First-Konfiguration.
The reason why I explicitly set the EntityState
is because I'm working with detached objects. If I wouldn't set the state of the Person
object it would be inserted as a new entry. To avoid this I mark it as Unchanged
.