28

I've read some questions/answers that have to do with this particular error message, but I'm not quite understanding the appropriate solution.

I've read many times that you should create the EF4 context, use it, then dispose of it. Throughout my application, I'm loading entities here and there using different context objects, and then eventually want to associate the entities together.

I've create a simple console application that easily causes the error. The very simple model is diagrammed followed by the code.

How can I get the two different entities to share the same context? Do I really have to create a new context, load the two entities again (even though I already have them), simply to associate them and save?

If I simply missed an already existing, appropriate question/answer, please point me to the right place.

Simple EF4 Model diagram

internal class Program {

    private static void Main(string[] args) {
        DeleteAllEntities();
        CreateInitialEntities();
        Owner o = LoadOwner();
        Child c = LoadChild();
        AssociateAndSave(o, c);
    }

    private static void AssociateAndSave(Owner o, Child c) {
        using (var context = new ModelEntities()) {
            // Exception occurs on the following line.
            o.Children.Add(c);
            context.Attach(o);
            context.SaveChanges();
        }
    }

    private static Owner LoadOwner() {
        using (var context = new ModelEntities()) {
            return ((from o in context.Owners
                     select o).First());
        }
    }

    private static Child LoadChild() {
        using (var context = new ModelEntities()) {
            return ((from c in context.Children
                     select c).First());
        }
    }

    private static void CreateInitialEntities() {
        using (var context = new ModelEntities()) {
            Owner owner = new Owner();
            Child child = new Child();
            context.Owners.AddObject(owner);
            context.Children.AddObject(child);
            context.SaveChanges();
        }
    }

    private static void DeleteAllEntities() {
        using (var context = new ModelEntities()) {
            List<Child> children = (from c in context.Children
                                    select c).ToList();
            foreach (var c in children)
                context.Children.DeleteObject(c);
            List<Owner> owners = (from o in context.Owners
                                  select o).ToList();
            foreach (var o in owners)
                context.Owners.DeleteObject(o);
            context.SaveChanges();
        }
    }
}
Steve
  • 6,334
  • 4
  • 39
  • 67
  • 1
    Hmm.. Simply calling `context.Attach(o); context.Attach(c);` prevents the error. But when I tried this in my actual application, I got `An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key` even though the entities were loaded in a similar manner to this example... – Steve Mar 07 '13 at 15:20

2 Answers2

22

You should get a reference to the child and owner objects in the same context. An approach for this would be to get the ids and then pass them as parameters to the AssociateAndSave(int oId, int cId) method.

Then, you retrieve the references to the objects in the same context and you make the attachment.

private static void Main(string[] args)
{
    DeleteAllEntities();
    CreateInitialEntities();
    int oId = LoadOwnerId();
    int cId = LoadChildId();
    AssociateAndSave(oId, cId);
}

private static int LoadOwnerId()
{
    using (var context = new ModelEntities())
    {
        return (from o in context.Owners
                select o).First().Id;
    }
}

private static int LoadChildId()
{
    using (var context = new ModelEntities())
    {
        return (from c in context.Children
                select c).First().Id;
    }
}

private static void AssociateAndSave(int oId, int cId)
{
    using (var context = new ModelEntities())
    {
        var owner = (from o in context.Owners
                        select o).FirstOrDefault(o => o.ID == oId);
        var child = (from o in context.Children
                        select o).FirstOrDefault(c => c.ID == cId);

        owner.Children.Add(child);
        context.Attach(owner);
        context.SaveChanges();
    }
}
Alex Filipovici
  • 31,789
  • 6
  • 54
  • 78
  • 2
    I actually started out doing this, but I figured it was a bit wasteful to query the database for the entity that I already had loaded previously from the database. – Steve Mar 07 '13 at 15:55
  • 1
    I decided to go with portions of both answers on this question, but unfortunately I can't mark them both as answers.. :) Anyone searching that finds this should also see http://stackoverflow.com/a/5695009/425871. – Steve Mar 12 '13 at 15:02
4

You cannot delete objects from one context, with an instance of another context, because each context tracks its objects separately.

One ideea would be to provide each of your methods a parameter of your context type so your operations are made within the same context.

EX:

private static void CrudOperationExample(DBContext contextInstane)
{
    //perform crud operations.
}

I you want to do it more nicely, i suggest you look for dependency injection, as it is very much helpful in ORM's .

Freeman
  • 5,691
  • 3
  • 29
  • 41
  • 1
    I used to keep a context alive so I could do this, but again and again I read that you should only keep the context for a very short time, when you need it. It's possible in my application that the context could be open for 5 minutes, or maybe even an hour. It would really depend on how long the user kept the window open. – Steve Mar 07 '13 at 15:54
  • 1
    Also - you say each context tracks its entities separately, but the context was disposed of (all my operations are in `using` blocks). – Steve Mar 07 '13 at 15:56