bigmac
bigmac

Reputation: 2553

Updating and Deleting Associated Records in a One-To-Many Relationship using Entity Framework

Using EF 4.1 Code First, I have a Member entity and it in turn has two "one-to-many" relationships for a HomeAddress and WorkAddress. It also has a boolean property to state whether or not to use either of these addresses.

I have two issues that I can't figure out:

  1. Whenever I update a member's address, a new record is added to the MemberAddresses table (with a new ID value) and the existing record is not deleted. Though it looks fine from the front-end perspective as the HomeAddressId and WorkAddressId in the parent Members table is updated with the new record, the old records are kept in the table (orhpaned). I don't want it to add a new address record when the address is updated. I only want it to update the existing record. If it has to add a new one, then I at least want it to clear out the old one.

  2. There are times that I want to delete the address record from the table. For example, if the member previously had an associated HomeAddress and later the DontUseHomeAddress is set to true, I want the address to be deleted from the table. So far, I have tried setting it to null, but that just prevents any updates. It doesn't delete it.

I'm sure there just some code piece I'm missing, so I'm including all the relevant code below that I think might affect this.

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

public class Member : Entity
{
  public string Name { get; set; }
  public bool DontUseHomeAddress { get; set; }
  public virtual MemberAddress HomeAddress { get; set; }
  public bool DontUseWorkAddress { get; set; }
  public virtual MemberAddress WorkAddress { get; set; }
  //... other properties here ...
}

public class MemberMap : EntityTypeConfiguration<Member>
{
  public MemberMap()
  {
    ToTable("Members");
    Property(m => m.Name).IsRequired().HasMaxLength(50);
    //TODO: Somehow this is creating new records in the MemberAddress table instead of updating existing ones 
    HasOptional(m => m.HomeAddress).WithMany().Map(a => a.MapKey("HomeAddressId"));
    HasOptional(m => m.WorkAddress).WithMany().Map(a => a.MapKey("WorkAddressId"));
  }
}

public class MemberAddressMap : EntityTypeConfiguration<MemberAddress>
{
  public MemberAddressMap()
  {
    ToTable("MemberAddresses");
    Property(x => x.StreetAddress).IsRequired().HasMaxLength(255);
    Property(x => x.City).IsRequired().HasMaxLength(50);
    Property(x => x.State).IsRequired().HasMaxLength(2);
    Property(x => x.ZipCode).IsRequired().HasMaxLength(5);
  }
}

Here is the InsertOrUpdate method from my repository class that my controller calls:

public class Repository<TEntity> : IRepository<TEntity> where TEntity : Entity
{
  private readonly EfDbContext _context;
  private readonly DbSet<TEntity> _dbSet;

  public Repository(EfDbContext context)
  {
    _context = context;
    _dbSet = _context.Set<TEntity>();
  }

  public bool InsertOrUpdate(TEntity entity)
  {
    if(entity.Id == 0)
    {
      _dbSet.Add(entity);
    }
    else
    {
      _context.Entry(entity).State = EntityState.Modified;
    }
    _context.SaveChanges();
    return true;
  }

  //... Other repository methods here ...
}

EDIT: Adding in code for UnitOfWork and MemberServices

public class MemberServices : IMemberServices { private readonly IUnitOfWork _unitOfWork; private readonly IRepository _memberRepository;

  public MemberServices(IUnitOfWork unitOfWork)
  {
    _unitOfWork = unitOfWork;
    _memberRepository = unitOfWork.RepositoryFor<Member>();
  }

  public Member Find(int id)
  {
    return _memberRepository.FindById(id);
  }

  public bool InsertOrUpdate(Member member)
  {
//    if(member.HomeAddress != null)
//      _unitOfWork.SetContextState(member.HomeAddress, EntityState.Modified);
//
//    if(member.WorkAddress != null)
//      _unitOfWork.SetContextState(member.WorkAddress, EntityState.Modified);
//
//    if(member.DontUseHomeAddress)
//    {
//      //TODO: This is an attempted hack... fix it by moving somewhere (possibly to repository)
//      var context = new EfDbContext();
//      context.Set<MemberAddress>().Remove(member.HomeAddress);
//      context.SaveChanges();
//    }  

    _memberRepository.InsertOrUpdate(member);
    return true;
  }
}

public class UnitOfWork : IUnitOfWork
{
  private readonly EfDbContext _context;

  public UnitOfWork()
  {
    _context = new EfDbContext();
  }

  public IRepository<T> RepositoryFor<T>() where T : Entity
  {
    return new Repository<T>(_context);
  }

  public void Attach(Entity entity)
  {
    _context.Entry(entity).State = EntityState.Unchanged;
  }

  public void SetContextState(Entity entity, EntityState state)
  {
    _context.Entry(entity).State = state;
  }

  public void Save()
  {
    _context.SaveChanges();

  }
}

Upvotes: 0

Views: 2167

Answers (1)

Slauma
Slauma

Reputation: 177133

Setting the state _context.Entry(entity).State = EntityState.Modified; doesn't affect the state of related entities. If you want to take care of changes of your related entities you must set their state to Modified as well:

if (member.HomeAddress != null)
    _context.Entry(member.HomeAddress).State = EntityState.Modified;
if (member.WorkAddress != null)
    _context.Entry(member.WorkAddress).State = EntityState.Modified;
_context.Entry(member).State = EntityState.Modified;

This is not generic anymore.

To delete an entity you have to call the appropriate method to delete an entity; setting the navigation property to null is not enough:

_context.MemberAddresses.Remove(member.HomeAddress);

Upvotes: 2

Related Questions