Eric K
Eric K

Reputation: 716

Solution to "An entity object cannot be referenced by multiple instances of IEntityChangeTracker" error

I am attempting to update a member, which has quite a few relationships, but receive an error saying that An entity object cannot be referenced by multiple instances of IEntityChangeTracker. Below are the various models and controllers that I have written. Any suggestions on how to correct this would be appreciate.

Member Model

public partial class Member
  {
    NCOWW_DB db = new NCOWW_DB();
    public Member()
    {
      this.Contacts = new HashSet<Contact>();
      this.Utilities = new HashSet<Utility>();
    }

    public int MemberID { get; set; }

    [Display(Name = "Member Name")]
    [Required]
    public string Name { get; set; }

    [Display(Name = "Member Number")]
    public string Number { get; set; }

    [Display(Name = "Mutual Aid Agreement On File")]
    public bool MutualAid { get; set; }

    [Display(Name = "Type ID")]
    public Nullable<int> TypeID { get; set; }

    [ForeignKey("TypeID")]
    public virtual UtilityType MemberType { get; set; }

    [Display(Name = "Water Population")]
    public Nullable<int> WaterPopulation { get; set; }

    [Display(Name = "Wastewater Population")]
    public Nullable<int> WastewaterPopulation { get; set; }

    private Address _Address = new Address();
    public Address Address { get { return _Address; } set { _Address = value; } }

    private Coordinates _Coordinates = new Coordinates();
    public Coordinates Coordinates { get { return _Coordinates; } set { _Coordinates = value; } }

    [NotMapped]
    public double Distance { get; set; }

    public bool Deleted { get; set; }

    [Display(Name = "Enable Member")]
    public bool Enabled { get; set; }

    private DateTime _createdOn = DateTime.UtcNow;
    public DateTime CreatedOn { get { return _createdOn; } set { _createdOn = value; } }

    private DateTime _modifiedOn = DateTime.UtcNow;
    public DateTime ModifiedOn { get { return _modifiedOn; } set { _modifiedOn = value; } }

    public System.Guid ModifiedBy { get; set; }

    //public virtual ICollection<Utility> Contacts { get; set; }
    private ICollection<Contact> _Contacts;
    public virtual ICollection<Contact> Contacts {
      get
      {
        return (from c in db.Contact
               where (c.MemberID == MemberID && c.Deleted == false)
               select c).OrderBy(c => c.FirstName).ToList(); 
      }
      set
      {
        _Contacts = value;
      }
    }

    //public virtual ICollection<Utility> Utilities { get; set; }
    private ICollection<Utility> _Utilities;
    public virtual ICollection<Utility> Utilities
    {
      get
      {
        return (from u in db.Utility
                where (u.MemberID == MemberID && u.Deleted == false)
                select u).OrderBy(u => u.Name).ToList();
      }
      set
      {
        _Utilities = value;
      }
    }
  }

Utility Model

  public partial class Utility
  {
    NCOWW_DB db = new NCOWW_DB();
    public Utility()
    {
      this.Contacts = new HashSet<Contact>();
      this.Resources = new HashSet<UtilityResource>();
    }
    [Key]
    public int UtilityID { get; set; }
    [Display(Name="Member")]
    [Required]
    public int MemberID { get; set; }
    [ForeignKey("MemberID")]
    public virtual Member Member { get; set; }
    [ForeignKey("UtilityID")]
    public virtual ICollection<UtilityResource> Resources { get; set; }
    [ForeignKey("UtilityID")]
    private ICollection<Contact> _Contacts;
    public virtual ICollection<Contact> Contacts
    {
      get
      {
        return (from c in db.Contact
                where (c.MemberID == MemberID && c.Deleted == false)
                select c).OrderBy(c => c.FirstName).ToList();
      }
      set
      {
        _Contacts = value;
      }
    }
    //public virtual ICollection<Contact> Contacts { get; set; }
    [Required]
    public string Name { get; set; }
    private Address _Address = new Address();
    public Address Address { get { return _Address; } set { _Address = value; } }
    private Coordinates _Coordinates = new Coordinates();
    public Coordinates Coordinates { get { return _Coordinates; } set { _Coordinates = value; } }
    [Display(Name = "Utility Type")]
    public int? TypeID { get; set; }
    [ForeignKey("TypeID")]
    [Display(Name = "Utility Type")]
    public virtual UtilityType UtilityType { get; set; }
    [Display(Name = "Region")]
    public int? RegionID { get; set; }
    [Display(Name = "County")]
    public int? CountyID { get; set; }
    private UtilityInfo _WaterInfo = new UtilityInfo();
    [Display(Name="Water Information")]
    public UtilityInfo WaterInfo { get { return _WaterInfo; } set { _WaterInfo = value; } }
    private UtilityInfo _WastewaterInfo = new UtilityInfo();
    [Display(Name = "Wastewater Information")]
    public UtilityInfo WastewaterInfo { get { return _WastewaterInfo; } set { _WastewaterInfo = value; } }
    [NotMapped]
    public double Distance { get; set; }
    private bool _enabled = true;
    public bool Enabled { get { return _enabled; } set { _enabled = value; } }
    public bool Deleted { get; set; }
    private DateTime _createdOn = DateTime.UtcNow;
    public DateTime CreatedOn { get { return _createdOn; } set { _createdOn = value; } }
    private DateTime _modifiedOn = DateTime.UtcNow;
    public DateTime ModifiedOn { get { return _modifiedOn; } set { _modifiedOn = value; } }
    public Guid ModifiedBy { get; set; }
  }

Contact Model

  public partial class Contact
  {
    public Contact()
    {
      this.Phones = new HashSet<ContactPhone>();
    }
    [Key]
    public int ContactID { get; set; }
    public Nullable<Guid> UserID { get; set; }
    [Display(Name = "Member")]
    public int MemberID { get; set; }
    public Nullable<int> UtilityID { get; set; }
    public string Username { get; set; }

    [Display(Name = "First Name")]
    [Required]
    public string FirstName { get; set; }
    [Display(Name = "Last Name")]
    [Required]
    public string LastName { get; set; }
    [NotMapped]
    public string FullName { get { return FirstName + " " + LastName; } }
    [NotMapped]
    public string FullNameLastNameFirst { get { return LastName + ", " + FirstName; } }
    [Display(Name = "Contact Type")]
    public string Type { get; set; }
    [RegularExpression(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*")]
    [Required]
    public string Email { get; set; }

    [ForeignKey("ContactID")]
    public virtual ICollection<ContactPhone> Phones { get; set; }

    [NotMapped]
    [Display(Name = "Grant Website Access")]
    public bool WebsiteAccess { get; set; }

    public bool Deleted { get; set; }
    private DateTime _createdOn = DateTime.UtcNow;
    public DateTime CreatedOn { get { return _createdOn; } set { _createdOn = value; } }

    private DateTime _modifiedOn = DateTime.UtcNow;
    public DateTime ModifiedOn { get { return _modifiedOn; } set { _modifiedOn = value; } }
    public System.Guid ModifiedBy { get; set; }

    public virtual Member Member { get; set; }
    public virtual Utility Utility { get; set; }

    [NotMapped]
    public string Password { get; set; }
    [NotMapped]
    [Compare("Password")]
    [Display(Name = "Confirm Password")]
    public string PasswordConfirm { get; set; }
  }
  public partial class ContactPhone
  {
    [Key]
    public int PhoneID { get; set; }
    public int ContactID { get; set; }
    public string Number { get; set; }
    public string Type { get; set; }
  }

Member Edit Controller

    private NCOWW_DB db = new NCOWW_DB(); //dbContext
    public ActionResult Edit(Member member)
    {
      try
      {
        if (ModelState.IsValid)
        {
          member.ModifiedBy = CurrentUser;
          member.ModifiedOn = DateTime.UtcNow;
          db.Entry(member).State = EntityState.Modified; <--ERROR OCCURS HERE!
          db.SaveChanges();

          //RETURN TO DASHBOARD
          return RedirectToAction("Index");
        }
      }
      catch (Exception err)
      {
        /*catch error code*/
      }
      return View(member);
    }

UPDATE

Based on the answer below, I have modified my code in an attempt to remove the ill-placed NCOWW_DB db = new NCOWW_DB(). However, this does not solve the problem as I am unsure how to pass the dbContext properly. Regardless, this is where the code currently stands:

UPDATED MEMBER MODEL

    ...
    private ICollection<Contact> _Contacts;
    public virtual ICollection<Contact> Contacts {
      get
      {
        return MemberFunctions.LoadMemberContacts(MemberID);
      }
      set
      {
        _Contacts = value;
      }
    }

    private ICollection<Utility> _Utilities;
    public virtual ICollection<Utility> Utilities
    {
      get
      {
        return MemberFunctions.LoadMemberUtilities(MemberID);
      }
      set
      {
        _Utilities = value;
      }
    }
  }
  public static class MemberFunctions
  {
    public static ICollection<Contact> LoadMemberContacts(int memberID)
    {
      NCOWW_DB db = new NCOWW_DB();
      return (from c in db.Contact
              where (c.MemberID == memberID && c.UtilityID == null && c.Deleted == false)
              select c).OrderBy(c => c.FirstName).ToList(); 
    }
    public static ICollection<Utility> LoadMemberUtilities(int memberID)
    {
      NCOWW_DB db = new NCOWW_DB();
      return (from u in db.Utility
              where (u.MemberID == memberID && u.Deleted == false)
              select u).OrderBy(u => u.Name).ToList();
    }
  }

Upvotes: 0

Views: 2906

Answers (2)

Kirill Bestemyanov
Kirill Bestemyanov

Reputation: 11964

When you set property of member it automatically get modified state so statement db.Entry(member).State = EntityState.Modified; is not needed.

But i'm not seeing where you attach your entity to dbcontext and it can be another problem in your code. It seems that you should call instead

db.Members.Attach(member);

Upvotes: 0

Slauma
Slauma

Reputation: 177133

You are working very carelessly with multiple context (that you even don't dispose properly). In every Member and Utility entity you instantiate a new context:

NCOWW_DB db = new NCOWW_DB();

and use it in property getters like Member.Contacts (even without a guard if the collection hasn't been loaded already, so you'll get a DB query everytime you access the property).

Your particular exception occurs because db.Entry(member).State = EntityState.Modified will attach the member entity and all related entities to the context db. In order to attach the related Contact entities EF must touch the property getter member.Contacts which will run the query in your getter that will attach the Contact entities to another context, namely the one you instantiate in the member entity. Attaching the same object to two different contexts is not allowed and causes the exception.

You should refactor your code somehow to make sure that you only use the same context instance for a DB operation. Especially I'd stay away from creating context members inside an entity. The better place for queries like that are repositories or data access or service layers or whatever you like to call them.

Upvotes: 3

Related Questions