Jeff Borden
Jeff Borden

Reputation: 1419

Adding/updating navigation properties in EF CF

I know that this has been asked multiple times here on SO, but I cannot find a definitive answer on how to add or update navigation properties in EF CF.

Entities (simplified):

 public class Basket : EntityBase
    {
        public virtual List<Fruit> TaggedFruits { get; set; }
    } 

    public class Fruit : EntityBase
        {

        }

ActionResult:

 [HttpPost]
        public ActionResult SaveTags(Basket basket, int[] selectedFruits)
        {
        basket.TaggedFruits = new List<Fruits>();

        foreach (int guid in selectedFruits)
            basket.TaggedFruits.Add(repository.Fruits.FirstOrDefault(p => p.Id == guid));

        using (var context = new EFDbContext())
        {
            context.Baskets.Attach(basket);
            context.SaveChanges();
        }

        return RedirectToAction("GetBasket", new {guid = basket.Guid});
    }

I've tried multiple iterations of the SaveTags method above, but never got it to work. This one throws:

An entity object cannot be referenced by multiple instances of IEntityChangeTracker.

After researching here and other sites, the error obviously states that mixing my repository pattern and the DBContext in the method is causing a conflict.

If I move the tagging to the repository, with the following methods:

 [HttpPost]
            public ActionResult SaveTags(Basket basket, int[] selectedFruits)
            {
                   List<Fruit> taggedFruits = new List<Fruit>();
           foreach (int guid in selectedFruits)
               taggedFruits.Add(new Fruit {Id = guid});

           libraryRepository.TagBasket(basket, taggedFruits);

            return RedirectToAction("GetBasket", new {guid = basket.Guid});
        }



/*in repository*/
    public void TagBasket(Basket basket, List<Fruit> fruits )
            {
                basket.Taggedfruits = new List<Fruit>();
                foreach (var fruit in fruits)
                {
                    basket.Taggedfruits.Add(Fruit);
                }


            context.Baskets.Attach(basket);
       context.SaveChanges();
        }

Then no changes are committed to the database. Nothing happens, at all. Can someone point me in the right direction? I've been fighting this for way to long, thanks...

Upvotes: 4

Views: 4280

Answers (1)

Slauma
Slauma

Reputation: 177133

Well, you are only attaching the entity (= setting state to Unchanged) and then call SaveChanges. You are telling EF in that case that nothing has changed -> nothing happens.

To change the relationships between Basket and TaggedFruits you must load the original fruits collection of the basket from the database and then either remove or add the tagged fruits from/to the loaded fruit collection of the basket according to the Ids in your Id collection:

public void TagBasket(Basket basket, List<Fruit> fruits)
{
    var basketInDB = context.Baskets.Include(b => b.Taggedfruits)
        .Single(b => b.Id == basket.Id);

    foreach (var fruitInDB in basketInDB.Taggedfruits.ToList())
        if (!fruits.Any(f => f.Id == fruitInDB.Id))
            basketInDB.Taggedfruits.Remove(fruitInDB);

    foreach (var fruit in fruits)
        if (!basketInDB.Taggedfruits.Any(f => f.Id == fruit.Id))
        {
            var newFruit = new Fruit { Id = fruit.Id };
            context.Fruits.Attach(newFruit);
            basketInDB.TaggedFruits.Add(newFruit);
        }

    // Next line is only necessary if other properties in basket
    // could have been changed in your view
    context.Entry(basketInDB).CurrentValues.SetValues(basket);

    context.SaveChanges();
}

You can also just pass in the int[] selectedFruits collection into this method because only the ids are needed.

Upvotes: 6

Related Questions