You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Entity Framework无法正确修改/删除Firm子项(地址、网站)问题

Entity Framework 6 Not Syncing Child Entity Deletions/Updates When Updating Parent 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 when SaveChanges is 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:25:38