在 EF Core 中更新导航属性将它们设置为 null

Updating navigational properties sets them to null in EF Core

当我尝试更新 Entity Framework Core 中的导航属性时,我遇到了一些异常行为,它将值设置为 null,然后从数据库中删除了记录。

简化示例:

public class Hla : BaseEntity
{
    public string AField1 { get; set; }
    public virtual Patient Patient { get; set; }
    public int PatientId { get; set; }
}

public class Patient : BaseEntity
{
    public string Test { get; set; }
    public virtual Hla Hla { get; set; }
}

public class PatientRepository : Repository, IPatientRepository
{
    private readonly SearchAndMatchContext _context;

    public PatientRepository(SearchAndMatchContext context) : base(context)
    {
        _context = context;
    }

    public async Task<Patient> RetrieveEntity(int id)
    {
        return await _context.Patients
            .Include(t=>t.Hla)
            .FirstOrDefaultAsync(t => t.Id == id);
    }

    public  void UpdateEntity(Patient entity)
    {
        _context.Patients.Update(entity);
    }
}

导致问题的代码:

private static Hla Map(Hla original, Hla update)
{
    var  fieldsToSkip = new string[]{"Id"};
    
    PropertyInfo[] fields = update.GetType().GetProperties();

    foreach (var field in fields)
    {
        if (fieldsToSkip.Contains(field.Name))
        {
            continue;
        }

        var newValue = field.GetValue(update);
        field.SetValue(original, newValue);
    }

    return original;
}

var patient = await _patientRepository.RetrieveEntity(1);

Map(patient.Hla, new Hla
    {
        AField1 = "dfgdfg",
        PatientId = patient.Id
    });

patientRepository.UpdateEntity(patient);
await _patientRepository.UnitOfWork.SaveChangesAsync();

一旦 SaveChangesAsync 被调用,Hla 就被设置为 null - 从直接 window:

输出

然而,如果我有一个简单的“直接”映射器,它似乎工作得很好。

更简单的映射器:

private static void Map(Hla current, Hla update)
{
    current.AField1 = update.AField1;
}

然后我立即得到 window 输出:

我有点不明白为什么会这样,这对我来说似乎没有意义。

如果我使用自动映射器等,也会发生这种情况。

我错过了什么?

方法 Map(Hla original, Hla update) 复制所有属性(Hla.Id 除外),这包括属性 Hla.PatientHla.PatientId。但是来自 Hla update 的副本没有耐心 :

var patient = await _patientRepository.RetrieveEntity(1);

Map(patient.Hla, new Hla
    {
        AField1 = "dfgdfg",
        PatientId = patient.Id
        //No patient set
    });

然后Map设置在原来的Patient <- null。 接下来 EF 检测修改,在 DB 中更新并应用于所有实体包括 Patient.

解决方案是从副本中排除属性 PatientPatientId:

private static Hla Map(Hla original, Hla update)
{
    var  fieldsToSkip = new string[]{"Id", "PatientId", "Patient"};

    PropertyInfo[] fields = update.GetType().GetProperties();
    foreach (var field in fields)
    {
        if (fieldsToSkip.Contains(field.Name))
        {
            continue;
        }
        var newValue = field.GetValue(update);
        field.SetValue(original, newValue);
    }
    return original;
}

编辑:其他解决方案是在实例副本中设置 属性 Patient :

Map(patient.Hla, new Hla
    {
        AField1 = "dfgdfg",
        PatientId = patient.Id,
        Patient = patient
    });

Map 在这里似乎用处不大。它不是用来映射的,它是用来在实体中设置单个属性的。这些字段是硬编码的,因此不会有任何收获。问题的代码可以替换为 :

var patient = await _patientRepository.RetrieveEntity(1);
patient.AField1 = "dfgdfg";
await _patientRepository.UnitOfWork.SaveChangesAsync();

Update 的调用也没有用,因为 EF 已经跟踪 patient 实体并将检测任何更改。 Update 只需要 untracked/detached 个对象,告诉 EF 开始跟踪处于 Updated 状态的对象。