Entity Framework无法正确修改/删除Firm子项(地址、网站)问题
Hey there, let's break down your problem and fix it step by step. I can see you've got the Angular side working correctly—when you delete addresses/websites, the API gets the right number of items—but EF6 is misbehaving by creating new records instead of updating existing ones, and not deleting the removed entries. Here's what's going wrong and how to fix it:
The Root Cause
Looking at your API controller code, the main issue is that you're directly replacing the child collections (firm.Addresses and firm.Websites) with new, untracked objects. Even though you set the ID property on these new objects, EF doesn't recognize them as existing entities because they aren't being tracked by the DbContext. Plus, your subsequent logic to remove/add addresses is operating on this new untracked collection, so it's completely ineffective for the actual database entities.
Fix Steps
1. Rewrite Child Entity Handling (Addresses & Websites)
Instead of replacing the entire collection, we need to individually handle each child entity's state: update existing ones, add new ones, and mark removed ones for deletion.
Update Address Logic
Replace your current address handling code in the controller with this:
// Get the existing tracked addresses from the firm var existingAddresses = firm.Addresses.ToList(); // Update or add addresses from the view model foreach (var modelAddr in model.Addresses) { var existingAddr = existingAddresses.FirstOrDefault(a => a.ID == modelAddr.AddressId); if (existingAddr != null) { // Update properties of the tracked existing address existingAddr.LINE1 = modelAddr.Line1; existingAddr.LINE2 = modelAddr.Line2; existingAddr.LINE3 = modelAddr.Line3; existingAddr.FIRM_ID = modelAddr.FirmId; // Add any other address properties you need to update here } else { // Create a new address and add it to the tracked collection var newAddr = new ADDRESS { LINE1 = modelAddr.Line1, LINE2 = modelAddr.Line2, LINE3 = modelAddr.Line3, FIRM_ID = modelAddr.FirmId, // Initialize other required properties here }; firm.Addresses.Add(newAddr); } } // Mark addresses not present in the view model for deletion var keepAddressIds = model.Addresses.Select(a => a.AddressId).ToList(); var addressesToDelete = existingAddresses.Where(a => !keepAddressIds.Contains(a.ID)).ToList(); foreach (var addr in addressesToDelete) { dbContext.Entry(addr).State = EntityState.Deleted; // If you only want to remove the association (not delete from DB), use: // firm.Addresses.Remove(addr); }
Update Website Logic
Do the exact same for websites:
var existingWebsites = firm.Websites.ToList(); foreach (var modelSite in model.Websites) { var existingSite = existingWebsites.FirstOrDefault(w => w.ID == modelSite.FirmWebsiteId); if (existingSite != null) { // Update tracked website properties existingSite.WEBSITE_URL = modelSite.WebsiteUrl; existingSite.USERNAME = modelSite.Username; existingSite.PASSWORD = modelSite.Password; existingSite.FIRM_ID = modelSite.FirmId; } else { // Add new tracked website var newSite = new FIRM_WEBSITE { WEBSITE_URL = modelSite.WebsiteUrl, USERNAME = modelSite.Username, PASSWORD = modelSite.Password, FIRM_ID = modelSite.FirmId, }; firm.Websites.Add(newSite); } } // Mark websites not in the view model for deletion var keepWebsiteIds = model.Websites.Select(w => w.FirmWebsiteId).ToList(); var sitesToDelete = existingWebsites.Where(w => !keepWebsiteIds.Contains(w.ID)).ToList(); foreach (var site in sitesToDelete) { dbContext.Entry(site).State = EntityState.Deleted; }
2. Adjust Your Repo's Update Method
Your current Repo<T>.Update method uses CurrentValues.SetValues(obj), which only updates scalar properties of the parent entity—it doesn't handle navigation properties (child collections). Since we've already handled all child entity states directly, you can simplify the Update logic to ensure EF saves all changes:
public T Update(T obj) { _logger.Info("Repo.Update {0}", obj); var entity = Get(obj.ID); var oldEntity = new T(); var entry = dbContext.Entry(entity); oldEntity.InjectFrom(entry.OriginalValues.ToObject()); // Update parent scalar properties entry.CurrentValues.SetValues(obj); // We've already handled child entities, so just save changes LogAllModifiedEntities(dbContext); dbContext.SaveChanges(); if (_watcherNotification != null) _watcherNotification.EntityChanged(ChangeNotificationType.Modified, entity, oldEntity); return Get(obj.ID); }
Why This Works
- We're working with the tracked entities that EF already knows about (from
firmService.GetWithIncludes(model.FirmId)), so EF recognizes when we update their properties. - For new entities, we add them to the tracked collection, so EF knows to insert them.
- For entities that were deleted on the frontend, we mark their state as
Deleted, so EF will remove them from the database whenSaveChangesis called.
Your Original Code References (For Context)
View Models
public class NewFirmViewModel { public int FirmId { get; set; } public string FirmName { get; set;} public Nullable<DateTime> DateFounded { get; set; } public ICollection<AddressViewModel> Addresses { get; set; } public ICollection<WebsiteViewModel> Websites { get; set; } public bool hasIntralinks { get; set; } } public class AddressViewModel { public int AddressId { get; set; } public string Line1 { get; set; } public string Line2 { get; set; } public string Line3 { get; set; } public string Phone { get; set; } public bool IsHeadOffice { get; set; } public int FirmId { get; set; } } public class WebsiteViewModel { private int FirmWebsiteId { get; set; } private string WebsiteUrl { get; set; } public string Username { get; set; } public string Password { get; set; } public int FirmId { get; set; } }
Entities
public class FIRM: Entity,IHasAUMs<FIRM_AUM> { public FIRM() { this.MANAGERSTRATEGies = new HashSet<MANAGERSTRATEGY>(); this.FIRM_ACTIVITY = new HashSet<FIRM_ACTIVITY>(); this.FIRM_AUMs = new HashSet<FIRM_AUM>(); this.FIRM_REGISTRATION = new HashSet<FIRM_REGISTRATION>(); Addresses = new HashSet<ADDRESS>(); Websites = new HashSet<FIRM_WEBSITE>(); } public string NAME { get; set; } public string SHORT_NAME { get; set; } public string ALTERNATE_NAME { get; set; } public bool? INTRALINKS_FIRM { get; set; } public Nullable<DateTime> DATE_FOUNDED { get; set; } public virtual ICollection<ADDRESS> Addresses { get; set; } public ICollection<FIRM_WEBSITE> Websites { get; set; } // Other properties omitted for brevity } public class ADDRESS : Entity { public string LINE1 { get; set; } public string LINE2 { get; set; } public string LINE3 { get; set; } public int FIRM_ID { get; set; } [ForeignKey("FIRM_ID")] public FIRM FIRM { get; set; } public bool IS_HEAD_OFFICE { get; set; } // Other properties omitted for brevity } public class FIRM_WEBSITE : Entity { private string _WEBSITE_URL; public string WEBSITE_URL { get { if (string.IsNullOrEmpty(_WEBSITE_URL)) return _WEBSITE_URL; try { var ubuilder = new System.UriBuilder(_WEBSITE_URL ?? ""); return ubuilder.Uri.AbsoluteUri; } catch (UriFormatException ex) { return _WEBSITE_URL; } } set { _WEBSITE_URL = value; } } public string USERNAME { get; set; } public string PASSWORD { get; set; } public int FIRM_ID { get; set; } [ForeignKey("FIRM_ID")] public FIRM FIRM { get; set; } }
内容的提问来源于stack exchange,提问作者Tom

