6

I am trying to figure out the best practices when updating an entity and all it's children. For example; I have an "Employer" update service that would update the employer entity and the "Address" entities of the employer and the "Phone" entities of each "Address". User may add new addresses to the existing employer or they may update the current addresses or they can delete some, same applies for the phones of each addresses. Could you help me to write the ideal code that would handle this scenario.

I am using EF7 rc1 and I use Automapper to map the Dto to Entity in my service.

public partial class Employer
{
   public int EmployerId { get; set; }
   public int Name { get; set; }

   [InverseProperty("Employer")]
   public virtual ICollection<Address> Address { get; set; }
}

public partial class Address
{
   public int AddressId { get; set; }
   public int Address1{ get; set; }
   public int City { get; set; }

   [ForeignKey("EmployerId")]
   [InverseProperty("Address")]
   public virtual Employer Employer { get; set; }

   [InverseProperty("Address")]
   public virtual ICollection<Phone> Phone { get; set; }
}

public partial class Phone
{
    public int PhoneId { get; set; }
    public string Number { get; set; }

    [ForeignKey("AddressId")]
    [InverseProperty("Phone")]
    public virtual Address Address { get; set; }
}

My service method;

public async Task<IServiceResult> Update(EmployerDto employer)
{
 var employerDbEntity = await _db.Employer
             .Include(a=>a.Address).ThenInclude(p=>p.Phone)
             .SingleOrDefaultAsync (a=>a.EmployerId == employer.EmployerId);


 //How to handle the update operation for children?

 var entity = Mapper.Map<Employer>(employer);
 HandleChildren(employerDbEntity,entity);

 await _db.SaveChangesAsync();
 ...
 ...
}
private void HandleChildren(Employer employerDbEntity,Employer entity)
{
        //Delete 
        foreach (var existing in employerDbEntity.Address.ToList())
        {
            if (!entity.Address.Any(a => a.AddressId == existing.AddressId))
                employerDbEntity.Address.Remove(existing);
        }
        //Update or Insert
        foreach (var address in entity.Address)
        {
            var existing = employerDbEntity.Address.SingleOrDefault(a =>a.AddressId == address.AddressId);
            //Insert
            if (existing == null)
            {
                employerDbEntity.Address.Add(address);
            }
            //Update
            else
            {
                Mapper.Map(address, existing);
            }
        }
 }
Adem Tas
  • 323
  • 1
  • 9

1 Answers1

0

This example looks like a decent way to handle child collections. Each collection has to be checked manually for the action performed. (Using generics sounds good, but always bites back in some way. Typically, performance.)

With that in mind, here's a few recommendations:

  • Move the child collection handling into separate methods/services.
  • If querying for existing entities, retrieve the whole collection in one query then iterate the results in memory.
  • Since you're writing async code, you can take advantage of processing the child collections in parallel! To do this, each operation should create it's own context. This explains why it's faster.

Here's an example using the recommendations:

private async Task UpdateAddresses(List<Address> addressesToUpdate)
{
    using(var context = new Context())
   {

      var existingAddressIds = await context.Addresses
              .Where(a => addressesToUpdate.Contains(a.AddressId))
              .ToListAsync()
              .ConfigureAwait(false);

      existingAddressIds.ForEach(a => context.Addresses.Remove(a));     

      await context.SaveChangesAsync().ConfigureAwait(false);    
   }
}
Community
  • 1
  • 1
ermish
  • 1,160
  • 2
  • 13
  • 26