scottdavidwalker
scottdavidwalker

Reputation: 1511

Updating navigational properties sets them to null in EF Core

I have a situation when I'm attempting to update navigational properties in Entity Framework Core and I'm getting some unusual behaviour where it sets the value to null which then removes the record from the database.

Simplified example:

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);
    }
}

The code that's causing the issue:

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();

As soon as the SaveChangesAsync is called, the Hla is being set to null - output from the immediate window:

enter image description here

Whereas, if I have a simple "direct" mapper, it seems to work perfectly.

More simple mapper:

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

Then I get the immediate window output as:

enter image description here

I'm a little bit confused about why this happens, it seems to make no sense to me.

This also happens if I use an automapper etc.

What am I missing?

Upvotes: 0

Views: 1067

Answers (2)

vernou
vernou

Reputation: 7590

The method Map(Hla original, Hla update) copy all properties (except Hla.Id), this include the properties Hla.Patient and Hla.PatientId. But the copy from Hla update hasn't patient :

var patient = await _patientRepository.RetrieveEntity(1);

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

Then Map set in the orignal Patient <- null. Next EF detect the modification, update in DB and apply in all entities include the Patient.

The solution is to exclude from the copy the properties Patient and PatientId:

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;
}

EDIT : Other solution is to set the property Patient in copy from instance :

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

Upvotes: 2

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131180

Map doesn't seem very useful here. It's not used to map, it's used to set a single property in an entity. The fields are hard-coded so nothing is gained by this. The question's code could be replaced with just :

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

The call to Update isn't useful either because EF already tracks the patient entity and will detect any changes. Update is only needed with untracked/detached objects, to tell EF to start tracking the object in the Updated state.

Upvotes: 0

Related Questions