Maturano
Maturano

Reputation: 1023

Entity Framework updating many to many

I am trying to find the suitable form of updating a many to many relationship but i am find some issues on it.

The application is an asp.net mvc with simple injector(set up per context)

I have an entity People which has an IEnumerable and also i have a entity Team which has an IEnumerable.

The People entity has some other fields like Description, Email, etc and in its View, there are some check boxes so the user can choose the Teams.

I had tried to search on the net for the best approach for updating a many to many relationship and all that i found was deleting everything in the third table that is created and then add the Teams again.

Under is what i am trying to do, but i am getting pk's already exists. I know it is happening because firstly i load the People entity with Find method(to remove the list of Teams inside a foreach) and after i try to Attach(when the error happens) the modified object to set it's State to Modified.

        public override void Modify(People obj)
    {
        var ppl = SearchById(obj.Id);

        if (ppl.Teams.Count > 0)
        {
            foreach (var team in ppl.Teams.ToList())
            {
                ppl.Teams.Remove(team);
            }
        }

        var entry = lpcContext.Entry(obj);

        if (lpcContext.Entry(obj).State == EntityState.Detached)   
            dbSet.Attach(obj);            

        entry.State = EntityState.Modified;
    }

To air it out some things, i am using the Unit Of Work pattern, so i SaveChanges later.

Are there any other approach or i have to remove the Teams one by one, SaveChanges and after that, update the object and SaveChanges again?

Upvotes: 0

Views: 1263

Answers (1)

Manish Kumar
Manish Kumar

Reputation: 370

Unfortunately, working with detached entities isnt that straight forward in EF (yet). Attach() in EF will work for connected entities only. That means if you load an object from DB, pass it on to a view (or page is asp.net). When you read the object back from that view/page, EF will not be tracking that object anymore. If you now try to use Attach(), you will get an error that the key already exists in the DBContext. To workaround this, you need to find the entry and make changes to the entity using SetValues(). Something like this:

public virtual void Update(T entity)
        {
            DbEntityEntry dbEntityEntry = DbContext.Entry(entity);

            if (dbEntityEntry.State == EntityState.Detached)
            {
                var pkey = _dbset.Create().GetType().GetProperty("Id").GetValue(entity);//assuming Id is the key column
                var set = DbContext.Set<T>();
                T attachedEntity = set.Find(pkey);
                if (attachedEntity != null)
                {
                    var attachedEntry = DbContext.Entry(attachedEntity);
                    attachedEntry.CurrentValues.SetValues(entity);
                }                    
            }                
        }

Please note that this will ignore any nested objects. Hence, you should make DB trip, and compare the object returned from DB to find out if you should invoke Add, Update or Delete on each child object. This is the best workaround I could find when working with disconnected objects in EF. I guess nHibernate doesnt have this bug. Last I read about this, Microsoft was going to work on this after EF 6.x. So, we'll have to wait for this, I guess. Please go through the below article to understand the issue (and possible solutions) in length:

http://blog.maskalik.com/entity-framework/2013/12/23/entity-framework-updating-database-from-detached-objects/

To talk about your specfic scenario, you should make a DB hit and find out if any new teams were selected or some existing team was dropped and call add or delete as appropriate by comparing the Team collection of People object returned by DB vs People object returned from view/page. To update the People object itself, you can use the Update() as given above.

Upvotes: 2

Related Questions