0

When I am creating a new A record, I know that A and B will be new entries. C will always be an existing entity so I must make sure not to create a new C object or my database will have duplicates and my logic will fail.

Here is a representation of my model:

public class A
{
    public int ID { get; set; }
    public string Value { get; set; }
    public virtual ICollection<B> Bs { get; set; }
}
public class B
{
    public int ID { get; set; }
    public string Value { get; set; }
    public virtual A A { get; set; }
    public virtual ICollection<C> Cs { get; set; }
}
public class C
{
    public int ID { get; set; }
    public string Value { get; set; }
    public virtual ICollection<B> Bs { get; set; }
}

And my seed method:

    protected override void Seed(IdentifyingBug.MyDbContext context)
    {
        context.Cs.AddOrUpdate(
            c => c.ID,
            new C() { ID = 101, Value = "An existing C value" },
            new C() { ID = 202, Value = "Another existing C value" },
            new C() { ID = 303, Value = "Again, another existing C value" }
            );
    }

Due to how my actual application is structured I cannot use the same context and I must add a dettached entity and manually modify the state myself.

The below code represents the general structure of my code and the exact error I am getting:

    public Dictionary<string, A> Received = new Dictionary<string, A>();

    public void CreateObject(string token)
    {
        var a = new A() { Value = "New value for A" };

        List<B> bList = new List<B>();

        bList.Add( new B() { Value = "New value for B1" });
        bList.Add( new B() { Value = "New value for B2" });

        foreach (var b in bList)
        {
            List<C> cList = new List<C>();

            using (var db = new MyDbContext())
            {
                foreach (var c in db.Cs)
                {
                    cList.Add(c);
                }
            }

            b.Cs = cList;
        }

        a.Bs = bList;

        Received.Add(token, a);
    }
    public async Task<A> GetObject(string token)
    {
        A a;

        while (!Received.TryGetValue(token, out a))
        {
            await Task.Delay(100);
        }

        return a;
    }

    public async void button1_Click(object sender, EventArgs e)
    {
        string token = "qwerty";

        CreateObject(token);

        A a = await GetObject(token);

        using (var db = new MyDbContext())
        {
            db.As.Add(a);

            // attempt to set all C entities to unchanged but fails because of duplicate primary key
            foreach (var b in a.Bs)
            {
                foreach (var c in b.Cs)
                {
                    db.Entry<C>(c).State = EntityState.Unchanged;
                }
            }

            db.SaveChanges();
        }
    }

This code results in the exception:

Saving or accepting changes failed because more than one entity of type 'Project.C' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model. Use the Entity Designer for Database First/Model First configuration. Use the 'HasDatabaseGeneratedOption" fluent API or 'DatabaseGeneratedAttribute' for Code First configuration.

Multiple Bs inside A can have the same C. How do I tell entity framework that the Cs with the same primary ID are to be treated as the same entity and not throw an error?

(Again to be clear, I must save the dettached object, I am unable to share the context)

Guerrilla
  • 13,375
  • 31
  • 109
  • 210
  • Why are you trying to set the Cs to unchanged? Also, I can't get past the message "explicitly set primary key values". I haven't seen that and it makes me wonder if you left out some code because I don't see where you are explicitly setting C's primary keys. I'm having trouble understanding what you are trying to do. – redwards510 Sep 09 '15 at 23:21
  • If you delete that foreach statement then it runs and every C will become duplicated in the database (I dont want this). I wasn't sure if I should use `Unchanged` or `Modified` but both give the same error. The goal is to stop `C`s being duplicated in the database. I know `A` & `B` are new and any `C` attached to `B` already exists in the database. – Guerrilla Sep 09 '15 at 23:30
  • Also, If I only have one `B` object then this code works. – Guerrilla Sep 09 '15 at 23:34
  • I forgot to add seed method. I have added it now – Guerrilla Sep 09 '15 at 23:43

1 Answers1

0

I tried to figure it out, but there is too much extra stuff going on here like the await and it's tough to reason about it with the abstract letters A,B,C. Here's some suggestions.

Change your model slightly to explicitly reference the foreign Ids instead of just relying on virtual properties. I don't like that many->many thing you have with B and C.

Consider adding just the C.Ids you want to reference in B instead of the whole C object.

Query your database with SQL and examine the data directly to make sure there is no weirdness like multiple C rows you didnt know about.

Check that you are doing the data migration correctly. (Running Update-Database)

redwards510
  • 1,802
  • 1
  • 17
  • 23