views:

4654

answers:

4

Hi,

I am trying to save my contact, which has references to ContactRelation (just the relationship of the contact, married, single, etc) and Country. But everytime I try to save my contact, which is validated I get the exception "ADO.Net Entity Framework An entity object cannot be referenced by multiple instances of IEntityChangeTracker"

public Contact CreateContact(Contact contact)
{
    _entities.AddToContact(contact); //throws the exception
    _entities.SaveChanges();
    return contact ;
}

I'm using a loosely coupled MVC design with Services and Repositories. I've read a lot of posts about this exception but none give me a working answer...

Thank you, Peter

+3  A: 

I've seen this before, you may have to convert the Reference field to an EntityKey before saving and then Load it after its saved. Try this code instead:

public Contact CreateContact(Contact contact){
    contact.ConvertContactRelationToReference();
    _entities.AddToContact(contact); 
    //throws the exception
    _entities.SaveChanges();
    contact.ContactRelation.Load();
    return contact;
}

public partial class Contact
{
  public void ConvertContactRelationToReference()
  {
    var crId = ContactRelation.Id;
    ContactRelation = null;
    ContactRelationReference.EntityKey = new EntityKey("MyEntities.ContactRelations", "Id", crId);
  }
}

Of course, some of this code may need to be changed depending on your exact database structure.

bendewey
thank you for pointing me in the right direction, are you ok if I set my own answer as the accepted answer?
Peter
If I helpped you can throw me an up vote. Your welcome to mark yourself as the selected answer, but you won't get any rep for it.
bendewey
I will as soon as I have enough reputation, thanks!
Peter
Could you explain why all of this is necessary for a seemingly simple Save() operation?
Ryan Shripat
Because L2E is used you need to save all the linked objects first before you can save the main object. Which makes sense otherwise you would create (in my example) an artist without it's contact object. This isn't allowed by the database design.
Peter
+8  A: 

[Update]
Because L2E is used you need to save all the linked objects first before you can save the main object. Which makes sense otherwise you would create (in my example) an artist without it's contact object. This isn't allowed by the database design.
[/Update]

Here's my implementation which worked.

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Artist artist, [Bind(Prefix = "Contact")] Contact contact, [Bind(Prefix = "Country")] Country country, [Bind(Prefix = "ContactRelationship")] ContactRelationship contactRelationship)
{
    ViewData["Countries"] = new SelectList(new CountryService(_msw).ListCountries().OrderBy(c => c.Name), "ID", "Name");
    ViewData["ContactRelationships"] = new SelectList(new ContactRelationshipService(_msw).ListContactRelationships().OrderBy(c => c.ID), "ID", "Description");

    country = _countryService.GetCountryById(country.ID);
    contact.Country = country;
    contactRelationship = _contactRelationshipService.GetContactRelationship(contactRelationship.ID);
    contact.ContactRelationship = contactRelationship;
    if(_contactService.CreateContact(contact)){
        artist.Contact = contact;
        if (_service.CreateArtist(artist))
            return RedirectToAction("Index");        
    }
    return View("Create");
}

And then in my ContactRepository :

public Contact CreateContact(Contact contact)
{
    _entities.AddToContact(contact); //no longer throws the exception
    _entities.SaveChanges();
    return contact ;
}

I also found on this website that it is best to keep the same context throughout the application so I'm now using a special Data class for this:

public class Data
{
    private static MyDBEntities _myDBEntities;
    public static MyDBEntities MyDBEntities
    {
        get
        {
            if (_myDBEntities == null)
            {
                _myDBEntities = new MyDBEntities ();
            }
            return _myDBEntities;
        }
        set { _myDBEntities = value; }
    }
}
Peter
Thanks Peter, I spend about an hour trying to look for the same solution. Thanks again
Geo
A: 

singleton class for returning the datacontext is the only way!!

jrdevisser
A: 

A singleton? but what happens with concurrency?

And what if I have a web service? will only one static context make it? no concurrency troubles?

Jakov
It will kick you in the ass :-) But there is a solution: http://samscode.com/index.php/2009/12/making-entity-framework-v1-work-part-1-datacontext-lifetime-management/
Peter