C
C#2y ago
schwartzmj

❔ Updating EF entity with object containing optional properties

I'm writing an API endpoint where you can pass in any number of parameters for an EF core entity. I then want to merge the given parameters/object into the entity to update only those fields. I'm new to C# and having issues getting this to work. I ended up trying AutoMapper but it is updating all the given fields and setting the rest to null.
38 Replies
schwartzmj
schwartzmjOP2y ago
Any hints to push me into the right direction?
S7s
S7s2y ago
so you want to update only some fields of the whole entity, is that current?
Jimmacle
Jimmacle2y ago
the pattern i've been using is a DTO with every field nullable, and overwrite the entity properties with any non-null properties from the DTO
schwartzmj
schwartzmjOP2y ago
yes. say i've got an entity with 10 properties that i want to be updatable like this. I make a DTO with those 10 properties and theyre all nullable. i want to be able to make a request to update any number of those 10 properties on that entity
Jimmacle
Jimmacle2y ago
i just go through and check each one, i don't use a mapping library
schwartzmj
schwartzmjOP2y ago
and overwrite the entity properties with any non-null properties from the DTO how do i do this part? i'm new to C# so you just loop over the parameters?
Jimmacle
Jimmacle2y ago
i just write it out, a loop would require reflection
schwartzmj
schwartzmjOP2y ago
oh. so you're hard coding the fields / checks
Jimmacle
Jimmacle2y ago
yep mapping libraries are only really good for 1:1 mappings, as soon as you try to do something else it gets messy
schwartzmj
schwartzmjOP2y ago
seems kind of nuts to "hard code" all this stuff in like 3 different spots like the model itself. then the dto. then all the checks on each field figured there'd be a more elegant way
Jimmacle
Jimmacle2y ago
why are you doing it in 3 spots? i do it in 2 but only because i have separate read and write models to maintain
schwartzmj
schwartzmjOP2y ago
i mean one of these models has like 25 properties. so i'm keeping a model, a DTO, and hard coded if !== null checks all in sync. just feels a little messy. i imagined that writing out the DTO and having .NET validate it, that my DTO would be a source of truth essentially. but i've got to check if those properties are all null anyways by hand for example in TypeScript world, i'd probably validate the request object, filter out null values, then just apply the object and update it
Jimmacle
Jimmacle2y ago
i mean, you could do reflection if you have a significant number of properties then you also need a convention for indicating which DTO properties map to properties on other entities or have the client send a DTO with all properties set, not just the modified ones
schwartzmj
schwartzmjOP2y ago
yeah... that's an option. i just liked the idea of only sending what needed to be updated, since it will usually only be one field anyway i appreciate your help... i'm finding it difficult to google for answers in the C#/.NET world but it might just be my lack of knowledge you're saying automapper wouldnt be a good use case for this though? i did try the following (not really certain what i was doing, but testing it out):
public class DropPatchProfile : Profile
{
public DropPatchProfile()
{
CreateMap<DropPatchDto, Drop>()
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
}
}
public class DropPatchProfile : Profile
{
public DropPatchProfile()
{
CreateMap<DropPatchDto, Drop>()
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
}
}
S7s
S7s2y ago
you need to figure out which properties have been updated and then set their states as updated checkout ChangeTracker
Jimmacle
Jimmacle2y ago
it might work, i've avoided mappers in general why would you do that manually? the change tracker tracks changes automatically, that's the point
S7s
S7s2y ago
well its not perfect thats why, those nulls considered as updated
Jimmacle
Jimmacle2y ago
right, it doesn't solve the problem it's not an EF problem, it's a matter of conditionally setting fields using some kind of indicator it has data to set
schwartzmj
schwartzmjOP2y ago
am i correct that C# doesn't have an undefined type? so essentially even if i "map" the object over, it still needs a value to map to (null or the actual value). so i suppose i'm saying that in my mapping, if the value is null, i need to grab the existing value from the entity
Jimmacle
Jimmacle2y ago
C# is a strongly typed language, everything has a specific type you could load the entity from the database, conditionally apply your DTO fields, then save it you could use nullable fields and null as a flag for "don't change this field" or some other way to list which properties changed
schwartzmj
schwartzmjOP2y ago
makes sense. going to play around more. i really appreciate your help
Jimmacle
Jimmacle2y ago
this is how i've been doing it
Jimmacle
Jimmacle2y ago
in case you're not familiar with the null coalescing operator ?? it picks the left side if the left side is not null, otherwise the right side
schwartzmj
schwartzmjOP2y ago
thank you. i may end up just going that route for this proof of concept, but im interested in learning these other alternatives for my own knowledge as well
Anton
Anton2y ago
this. Get the entity from the db, then map your DTO into the entity (mapper.Map(source, dest)). Change tracking of EF will generate the right update query for you. nested updates are messier and depend on what you want specifically but yeah, that's not optimal if you've got only a couple of updated fields since you'd be loading the whole entity from the db
schwartzmj
schwartzmjOP2y ago
schwartzmj
schwartzmjOP2y ago
i also end up with like 60 lines of this in my mapper profile... lol.
jcotton42
jcotton422y ago
seems like more work than hand rolling the ampping tbh
schwartzmj
schwartzmjOP2y ago
yeah it does. coming from typescript (and similar languages), it just seems crazy to me that theres nothing built in to do what i want here. but i guess i get it so wasnt sure what i was missing
foreach (var property in input.GetType().GetProperties())
{
var value = property.GetValue(input);
if (value != null)
{
var columnName = property.Name;
drop.GetType().GetProperty(columnName)?.SetValue(drop, value);
}
}
_context.Drops.Update(drop);
await _context.SaveChangesAsync();
foreach (var property in input.GetType().GetProperties())
{
var value = property.GetValue(input);
if (value != null)
{
var columnName = property.Name;
drop.GetType().GetProperty(columnName)?.SetValue(drop, value);
}
}
_context.Drops.Update(drop);
await _context.SaveChangesAsync();
i did test that (used copilot to fill it in) but not entirely sure if thats even a good thing to do or not. i would also likely have to check and block certain columns from being updated. i also tested creating an endpoint like {id}/{columnName} but ran into issues validating that the value matched the type of the column though i did get the valid columnName validation working
Anton
Anton2y ago
ForAllMembers thing should work, the one from above
schwartzmj
schwartzmjOP2y ago
except for members that i have to override, like navigation properties, correct? i didn't see a way to set specific ones and then default to ForAllMembers for others
Anton
Anton2y ago
you can ignore the navigation properties separately or just specify MemberList.Source as a parameter to CreateMap you don't have to make you navigation properties virtual unless you're using lazy loading btw
schwartzmj
schwartzmjOP2y ago
weird, every time i've tried to ignore those properties and then ForAllMembers, it doesn't work figured the ForAllMembers overrides the ignore
Anton
Anton2y ago
actually there's a ForAllOtherMembers
schwartzmj
schwartzmjOP2y ago
that doesn't exist anymore / was removed but hate to go back and forth on this. just letting you know
Anton
Anton2y ago
you mean you don't want me to help?
schwartzmj
schwartzmjOP2y ago
no, of course i do. just don't want anybody to feel obligated - my last few responses have felt like i'm just shooting stuff down
Accord
Accord2y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server