views:

282

answers:

1

If I run the following code it throws the following error:

An entity object cannot be referenced by multiple instances of IEntityChangeTracker

 public void Save(Category category)
        {
            using(var db = new NorthwindContext())
            {
                if(category.CategoryID == 0) 
                {
                    db.AddToCategorySet(category); 
                }

                else
                {
                    //category.RemoveTracker();
                    db.Attach(category);
                }

                db.SaveChanges(); 
            }
        }

The reason is of course that the category is sent from interface which we got from GetById method which already attached the EntityChangeTracker to the category object. I also tried to set the entity tracker to null but it did not update the category object.

  protected void Btn_Update_Category_Click(object sender, EventArgs e)
        {
            _categoryRepository = new CategoryRepository();
            int categoryId = Int32.Parse(txtCategoryId.Text);

            var category = _categoryRepository.GetById(categoryId);

            category.CategoryName = txtUpdateCategoryName.Text; 

            _categoryRepository.Save(category);
        }
A: 

I'm still learning Entity Framework myself, but maybe I can help a little. When working with the Entity Framework, you need to be aware of how you're handling different contexts. It looks like you're trying to localize your context as much as possible by saying:

public void Save(Category category)
{
    using (var db = new NorthwindContext())
    {
        ...
    }
}

... within your data access method. Did you do the same thing in your GetById method? If so, did you remember to detach the object you got back so that it could be attached later in a different context?

public Category GetById(int categoryId)
{
    using (var db = new NorthwindContext())
    {
        Category category = (from c in db.Category where Category.ID == categoryId select c).First();
        db.Detach(category);
    }
}

That way when you call Attach it isn't trying to step on an already-attached context. Does that help?

As you pointed out in your comment, this poses a problem when you're trying to modify an item and then tell your database layer to save it, because once an item is detached from its context, it no longer keeps track of the changes that were made to it. There are a few ways I can think of to get around this problem, none of them perfect.

  1. If your architecture supports it, you could expand the scope of your context enough that your Save method could use the same context that your GetById method uses. This helps to avoid the whole attach/detach problem entirely, but it might push your data layer a little closer to your business logic than you would like.
  2. You can load a new instance of the item out of the new context based on its ID, set all of its properties based on the category that is passed in, and then save it. This costs two database round-trips for what should really only need one, and it isn't very maintainable.
  3. You can dig into the context itself to mark the Category's properties as changed.

For example:

public void Save(Category category)
{
    using (var db = new NorthwindContext())
    {
        db.Attach(category);
        var stateEntry = db.ObjectStateManager.GetObjectStateEntry(category);
        foreach (var propertyName in stateEntry.CurrentValues.DataRecordInfo.FieldMetadata.Select(fm => fm.FieldType.Name)) {
            stateEntry.SetModifiedProperty(propertyName);
        }
        db.SaveChanges();
    }
}

This looks a little uglier, but should be more performant and maintainable overall. Plus, if you want, you could make it generic enough to throw into an extension method somewhere so you don't have to see or repeat the ugly code, but you still get the functionality out of it.

StriplingWarrior
Thanks but when the category object comes to the Save method its EntityState says that it is unchanged.
Good point. I updated my post with some more suggestions.
StriplingWarrior