Entity Framework - how to merge child collections?

2019-06-03 23:05发布

问题:

Following is the exact scenario in my application.

There are two entities:

  • Customer: CustomerId, CustomerName
  • CustomerAddress: AddressId, CustomerId (FK), Area, City

A customer has one-to-many relation with CustomerAddress. A customer can have addresses of more than one cities.

When I save changes to the existing customer - at that time, its underlying CustomerAddress collection will be having all addresses of one particular city only.

So when I update that customer in the database, it should -

  • Add all new addresses of that city for the customer (i.e. the one exists in collection but not in db for that customer)
  • Retain those addresses which are common in both
  • Delete all those addresses which exist in database but not in collection for that city.

I know one method, wherein I can fetch Customer entity from database, and can add, delete, retain by looping through Addresses collection there.

But I am more interested in knowing the best practices to achieve this.

Any idea on this much appreciated?

回答1:

This is what I normally do, just like you said, looping through child navigations, that means loads everything into memory with a single round trip and then process the logic in the application, that's the main reason we use ORM.

// Loads contacts.
if (customerDb.Id != Guid.Empty)
{
    context.Entry(customerDb).Collection(c => c.customercontactxrefs).Load();
    foreach (var xref in customerDb.customercontactxrefs)
    {
        context.Entry(xref).Reference(x => x.contact).Load();
    }
}

// Deletes missing contacts.
var deletedXrefs = customerDb.customercontactxrefs.Where(xrefDb => !customer.Contacts.Any(contact => xrefDb.ContactId == contact.Id)).ToArray();
foreach (var xref in deletedXrefs)
{
    customerDb.customercontactxrefs.Remove(xref);
    context.Set<customercontactxref>().Remove(xref);
}

// Edits existing contacts.
foreach (var xrefDb in customerDb.customercontactxrefs)
{
    var foundContact = customer.Contacts.FirstOrDefault(contact => contact.Id == xrefDb.ContactId);
    if (foundContact != null && xrefDb.contact != null)
    {
        xrefDb.contact.Name = foundContact.Name;
        xrefDb.contact.Phone = foundContact.Phone;
        xrefDb.contact.Mobile = foundContact.Mobile;
        xrefDb.contact.Fax = foundContact.Fax;
        xrefDb.contact.Email = foundContact.Email;
    }
}

// Adds new contacts.
var newContacts = customer.Contacts.Where(contact => contact.Id == Guid.Empty).ToArray();
foreach (var contact in newContacts)
{
    customerDb.customercontactxrefs.Add(new customercontactxref
    {
        Id = Guid.NewGuid(),
        contact = new contact
        {
            Id = Guid.NewGuid(),
            Name = contact.Name,
            Phone = contact.Phone,
            Mobile = contact.Mobile,
            Fax = contact.Fax,
            Email = contact.Email
        }
    });
}

Or

using (RSDContext context = new RSDContext())
{
    var details = order.OrderDetails;
    order.OrderDetails = null;

    context.Entry(order).State = EntityState.Modified;
    foreach (var detail in details)
    {
        if (detail.Id == 0)
        {
            // Adds.
            detail.OrderId = order.Id;
            context.Entry(detail).State = EntityState.Added;
        }
        else if (detail.IsDeleted)
        // Adds new property called 'IsDeleted' 
        //  and add [NotMapped] attribute 
        //  then mark this property as true from the UI for deleted items.
        {
           // Deletes.
           context.Entry(detail).State = EntityState.Deleted;
        }
        else
        {
           // Updates.
           context.Entry(detail).State = EntityState.Modified;
        }
    }

    order.OrderDetails = details;
    context.SaveChanges();
}