Reputation: 1511
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:
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:
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
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
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