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

.NET Core中EmployeeInfo表增改逻辑异常问题求助

解决方案:EF Core实现EmployeeInfo的Upsert(存在更新,不存在插入)

问题根源

  1. 直接设置实体状态报错:你传入的employee是表单提交的脱离上下文对象,其EmployeeInfoId大概率为0。将状态设为Modified时,EF会尝试更新主键为0的记录,但数据库中无对应行,因此触发并发异常。
  2. 直接调用Update仅插入不更新:同样因为传入实体的主键为0,EF默认将其判定为新实体,执行插入而非更新操作。

正确实现方式

方法一:查询+属性映射(兼容所有EF Core版本)

先按EmployeeNumber和PolicyYear(业务唯一键)查询数据库,存在则更新属性,不存在则新增。这种方式能正确处理导航属性,避免主键不匹配问题:

public async Task UpdateEmployee(EmployeeInfo employee)
{
    // 查询数据库中是否存在目标记录
    var existingEmployee = await _ackContext.EmployeeInfos
        .Include(e => e.JobTitleLookup)
        .Include(e => e.LocationLookup)
        .FirstOrDefaultAsync(x => x.EmployeeNumber == employee.EmployeeNumber 
                                  && x.PolicyYear == employee.PolicyYear);

    if (existingEmployee != null)
    {
        // 复制提交的属性值到数据库已加载的实体
        _ackContext.Entry(existingEmployee).CurrentValues.SetValues(employee);

        // 处理JobTitleLookup导航属性
        if (employee.JobTitleLookup != null)
        {
            if (existingEmployee.JobTitleLookup == null)
            {
                _ackContext.JobTitleLookups.Attach(employee.JobTitleLookup);
                existingEmployee.JobTitleLookup = employee.JobTitleLookup;
            }
            else
            {
                _ackContext.Entry(existingEmployee.JobTitleLookup).CurrentValues.SetValues(employee.JobTitleLookup);
            }
        }

        // 处理LocationLookup导航属性
        if (employee.LocationLookup != null)
        {
            if (existingEmployee.LocationLookup == null)
            {
                _ackContext.LocationLookups.Attach(employee.LocationLookup);
                existingEmployee.LocationLookup = employee.LocationLookup;
            }
            else
            {
                _ackContext.Entry(existingEmployee.LocationLookup).CurrentValues.SetValues(employee.LocationLookup);
            }
        }
    }
    else
    {
        // 附加已有Lookup实体,避免重复插入
        if (employee.JobTitleLookup != null && employee.JobTitleLookup.JobTitleLookupId > 0)
        {
            _ackContext.JobTitleLookups.Attach(employee.JobTitleLookup);
        }
        if (employee.LocationLookup != null && employee.LocationLookup.LocationLookupId > 0)
        {
            _ackContext.LocationLookups.Attach(employee.LocationLookup);
        }

        // 新增实体
        _ackContext.EmployeeInfos.Add(employee);
    }

    await _ackContext.SaveChangesAsync();
}

方法二:EF Core 7+原生批量更新(性能优化)

如果你使用EF Core 7及以上版本,可以用ExecuteUpdate实现无实体加载的高效更新:

public async Task UpdateEmployee(EmployeeInfo employee)
{
    // 尝试更新现有记录
    var updatedCount = await _ackContext.EmployeeInfos
        .Where(x => x.EmployeeNumber == employee.EmployeeNumber 
                  && x.PolicyYear == employee.PolicyYear)
        .ExecuteUpdateAsync(setters => setters
            .SetProperty(e => e.LastName, employee.LastName)
            .SetProperty(e => e.FirstName, employee.FirstName)
            .SetProperty(e => e.DateFormFilled, employee.DateFormFilled)
            // 按需添加其他需要更新的属性
        );

    if (updatedCount == 0)
    {
        // 无匹配记录,执行插入
        if (employee.JobTitleLookup != null && employee.JobTitleLookup.JobTitleLookupId > 0)
        {
            _ackContext.JobTitleLookups.Attach(employee.JobTitleLookup);
        }
        if (employee.LocationLookup != null && employee.LocationLookup.LocationLookupId > 0)
        {
            _ackContext.LocationLookups.Attach(employee.LocationLookup);
        }

        _ackContext.EmployeeInfos.Add(employee);
        await _ackContext.SaveChangesAsync();
    }
}

关键注意事项

  • 唯一约束:确保数据库中EmployeeNumber和PolicyYear的组合设置了唯一约束,避免重复记录。
  • 导航属性处理:如果JobTitleLookup和LocationLookup是数据库已有数据,必须先调用Attach将其加入上下文,否则EF会尝试插入新的Lookup记录。
  • 属性复制优化:如果实体属性较多,可使用AutoMapper的Mapper.Map(employee, existingEmployee)代替手动属性复制,简化代码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 20:47:27