2

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.

7
  • I have encountered a setup like this many times but never ran in this particular problem. Can you post the full exception? Commented Oct 4, 2018 at 15:55
  • And can you explain why you are using context.Entry(creator).State = EntityState.Unchanged; to mark the state? If the entities are tracked it shouldn't be necessary. Commented Oct 4, 2018 at 15:56
  • @Stefan Please see my edits. Commented Oct 6, 2018 at 12:35
  • Ha, yes, that's what's surprises me; it seems that marking as unchanged still lead to inserting in the database. Anyhow; I think you are pushing the ORM to hard, keep in mind that it's an full blown repository. Keeping a long lived Db context etc. can lead to some annoying maintainability issues. I know this doesn't help you much in this case but I would suggest to keep your ORM (entityframework) calls "short" and isolated. For an example with domain models (which might help you) see: stackoverflow.com/questions/23648832/… Commented Oct 7, 2018 at 8:23
  • Thanks for detailed clarification, but actually I think that using detached objects like I do results in NOT keeping a long lived Db context and also keeping the ORM calls short ... Commented Oct 7, 2018 at 9:20

1 Answer 1

0

You could just set the Id's:

using (var context = new MyEntities())
{
    Task task = new Task()
    {
        Text = text,
        CreatorId = creator.Id,
        ReceiverId = receiver.Id
    };
    context.Task.Add(task);
    context.SaveChanges();
}
Sign up to request clarification or add additional context in comments.

1 Comment

Yes but then I have to load each object again after saving it as I need the navigation properties in the view afterwards. So thats not optimal.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.