1

I have a many to many relation that I want to update using a detached entity that I have attached to the context (deserilized from WebApi).

I have created this helper method that works for Many to one relations

public void SetEntityState<TEntity>(IEnumerable<TEntity> source) where TEntity : EntityBase, IDeletable
{
    foreach (var deletable in source.ToList())
    {
        var entry = Entry(deletable);
        if (deletable.Deleted)
            entry.State = EntityState.Deleted;
        else
            entry.State = deletable.Id == 0 ? EntityState.Added : EntityState.Modified;
    }
}  

Used like

db.Entry(user).State = EntityState.Modified; 

db.SetEntityState(user.HomeFolders); //Works because HomeFolders is a many to one relation    
db.SetEntityState(user.Roles) //Does not work because Roles is a many to many relation

This does not ofcourse work with a Many to many relation because entry.State = EntityState.Deleted will point to the referenced enity and try to delete that instead of the row in the relation table.

So how can I delete/add many to many relations on a newly attached entity?

edit: Config

public class UserConfiguration : EntityTypeConfiguration<User>
{
    public UserConfiguration()
    {
        HasKey(u => u.Id);

        Property(u => u.Username)
            .IsRequired()
            .HasMaxLength(50);

        Property(u => u.Password)
            .IsRequired()
            .HasMaxLength(128);

        HasMany(s => s.HomeFolders)
            .WithRequired()
            .Map(
                m => m.MapKey("UserId")
            );

        HasMany(p => p.Roles)
            .WithMany()
            .Map(m =>
            {
                m.ToTable("UserRole");
                m.MapLeftKey("UserId");
                m.MapRightKey("RoleId");
            });

        ToTable("User");
    }
}

Models

public abstract class EntityBase
{
    public int Id { get; set; }
}

public class Role : EntityBase,  IDeletable
{
    public string Name { get; set; }
    public bool Deleted { get; set; }
}

public class User : EntityBase
{
    public string Username { get; set; }
    public string Password { get; set; }

    public virtual ICollection<HomeFolder> HomeFolders { get; set; }
    public virtual ICollection<Role> Roles { get; set; }
}

update: Delete actually works if I remove the Role after the user is attached. But new roles that is in teh list will not be added

Update code as of latest revision, Add role still not working

public void UpdateUser(User user)
{

    db.Entry(user).State = EntityState.Modified;

    db.SetEntityState(user.HomeFolders);
    user.Roles.Where(r => r.Deleted).ToList().ForEach(r => user.Roles.Remove(r));

    if (string.IsNullOrEmpty(user.Password))
        db.Entry(user).Property(x => x.Password).IsModified = false;
    else
    {
        SetPassword(user);
    }

    db.SaveChanges();
}
Anders
  • 17,306
  • 10
  • 76
  • 144
  • To delete you can use [cascade delete](http://stackoverflow.com/questions/17487577/entity-framework-ef-code-first-cascade-delete-for-one-to-zero-or-one-relations). The SQL Server deletes all dependent records while deleting master record. I'm not sure about changing `State`, but the method `Add` can insert dependent records together with master record. – Mark Shevchenko Nov 18 '15 at 11:45
  • I tried just to remove the item from the collection that did nothing, do you have an example? – Anders Nov 18 '15 at 11:47
  • sorry, I was just not very well understand your question. – Mark Shevchenko Nov 18 '15 at 12:02
  • I'm not trying to do a cascading delete, I'm trying to delete a relation between two entities User and Role – Anders Nov 18 '15 at 12:12
  • I found the [article](http://sanderstechnology.com/2013/solving-the-detached-many-to-many-problem-with-the-entity-framework/12505/#.VkxtBIvouUk) with similar technique. Author uses the `ObjectContext.ObjectStateManager.ChangeRelationshipState` method. See chapter *Removing a many-to-many Relationship* in the article. – Mark Shevchenko Nov 18 '15 at 12:25
  • In my opinion, if you don't want to manipulate many to many associations as *independent associations* (i.e. without FK property) you should pull the junction class into the class model. Don't dive too deeply into the EF tracking mechanism. It makes you code hard to maintain when new EF versions are rolled out. – Gert Arnold Nov 19 '15 at 10:21

1 Answers1

0

Why do you chose the hard path and deal with EntityState?

Simply remove the roles, if user is attached:

user.roles.ToList().ForEach(r=> user.Roles.Remove(r))
Alireza
  • 10,237
  • 6
  • 43
  • 59
  • I have tried that both before and after user is attached to context – Anders Nov 18 '15 at 12:19
  • Is this Code-First? If so, please post the model. Anyway, I'm not convinced modifying `EntityState` is the best solution... – Alireza Nov 18 '15 at 12:20
  • Updated with config and models – Anders Nov 18 '15 at 12:34
  • Did you check state of attached user `Role`s ? – Alireza Nov 18 '15 at 13:09
  • You didn't say about state of attached user `Role`s. If you attach `user` the `Role`s will get `UnChanged` state – Alireza Nov 19 '15 at 01:51
  • But the Role is unchanged, its the UserRole relation that is changed – Anders Nov 19 '15 at 08:16
  • There is no `UserRole` relation in terms of POCO (C# classes). In C# you have to think in properties. When you manipulate `user.Roles` property then `user` is marked changed because one of its properties change. But when you attach that changed 'user', the already made changes will be lost. Why? Because a side effect of attaching an entity is marking it `Unchanged` and no matter what you have done to the entity, context thinks it's not changed. Hence, context exclude it to inspect the introduced changes – Alireza Nov 19 '15 at 09:14
  • Could you please show me in code how I should make EF understand that roles have been added – Anders Nov 19 '15 at 09:16
  • First attach the user, then manipulate the relation i.e. `Roles` property. – Alireza Nov 19 '15 at 09:17
  • But that is what I have done, db.Entry(user).State = EntityState.Modified; edit: I added db.Set().Attach(user); before setting state to Modified, changes nothing. Can remove roles, but new roles are not added – Anders Nov 19 '15 at 09:20