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

如何克隆SnapshotCompaniesHouseSearchResults并保存关联子表新父ID?

解决克隆父表及关联子表数据并关联新父项ID的问题

问题原因

当前代码仅修改了父表实体的JobId并批量插入,但存在以下核心问题:

  1. 直接操作上下文跟踪的原实体,EF Core可能误将其识别为更新操作而非新增
  2. 未处理关联子表:子实体仍绑定原父实体主键,未生成新副本插入数据库
  3. 未建立新父实体与新子实体的外键关联

解决方案步骤

  1. 查询原数据时禁用上下文跟踪,避免干扰新实体插入
  2. 为每个原父实体创建全新副本,设置新JobId及主键(根据主键生成策略处理)
  3. 为每个原子实体创建全新副本,关联新父实体的主键
  4. 先批量插入父表数据,再批量插入子表数据(若父主键为数据库生成,需配置批量插入返回新主键)

修改后的代码实现

克隆方法更新

private async Task CopySnapshotCompanyHouseData(Guid jobIdOld, Guid jobIdNew)
{
    // 禁用上下文跟踪,避免原实体干扰新实体插入逻辑
    var originalData = await _dbContext.SnapshotCompaniesHouseSearchResults
        .Where(x => x.JobId == jobIdOld)
        .Include(x => x.SnapshotCompaniesHousePreviousNames)
        .AsNoTracking()
        .ToListAsync();

    var newParents = new List<SnapshotCompaniesHouseSearchResults>();
    var newChildren = new List<SnapshotCompaniesHousePreviousNames>();

    foreach (var originalParent in originalData)
    {
        // 创建新父实体副本,复制非主键、非关联集合的属性
        var newParent = new SnapshotCompaniesHouseSearchResults
        {
            JobId = jobIdNew,
            CompanyName = originalParent.CompanyName,
            RegistrationNumber = originalParent.RegistrationNumber,
            // ... 补充其他需要克隆的父实体属性
            
            // 若主键为客户端生成(如Guid),生成新主键
            Id = Guid.NewGuid()
            // 若主键为数据库自增(如int),无需设置,插入后由EF自动填充
        };
        newParents.Add(newParent);

        // 处理关联子实体,创建副本并绑定新父实体主键
        foreach (var originalChild in originalParent.SnapshotCompaniesHousePreviousNames)
        {
            var newChild = new SnapshotCompaniesHousePreviousNames
            {
                PreviousName = originalChild.PreviousName,
                ChangeDate = originalChild.ChangeDate,
                // ... 补充其他需要克隆的子实体属性
                
                // 关联新父实体的主键
                SnapshotCompaniesHouseSearchResultId = newParent.Id,
                // 若子实体主键为客户端生成,生成新主键
                Id = Guid.NewGuid()
            };
            newChildren.Add(newChild);
        }
    }

    // 先插入父表,确保子表能获取到有效父主键
    await _dbContext.AbleBulkInsertAsync(newParents);
    await _dbContext.AbleBulkInsertAsync(newChildren);
}

批量插入扩展方法更新(针对数据库生成主键场景)

如果父实体主键是数据库自增类型(如int),需修改批量插入配置,启用返回插入后的主键值,确保子实体能正确关联:

public static async Task AbleBulkInsertAsync<T>(this DbContext dbContext, List<T> entities, int pageSize = 1000) where T : class
{
    int pageNumber = 0;
    while (true)
    {
        var page = entities.Skip(pageNumber * pageSize).Take(pageSize).ToList();
        if (page.Count == 0) return;

        // 启用SetOutputIdentity,让EF返回插入后的主键值
        await dbContext.BulkInsertAsync(page, new BulkConfig 
        { 
            BulkCopyTimeout = 0,
            SetOutputIdentity = true 
        });
        pageNumber++;
    }
}

关键注意点

  • 禁用跟踪:必须使用AsNoTracking(),避免原实体被上下文跟踪导致插入冲突
  • 实体副本:必须创建全新实体对象,不能直接修改原查询实体,确保EF识别为新增操作
  • 外键绑定:子实体的外键必须指向新父实体的主键,而非原父实体
  • 主键策略:根据实体主键生成方式(客户端/数据库)调整主键赋值逻辑

内容的提问来源于stack exchange,提问作者Eugene Sukh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 13:35:22