C
C#2d ago
y4hira

Updating/Deleting owned properties with EF Core and AutoMapper

The problem I'm facing is that automapper is not tracking the changes since when i retrive AsNotracking() it wont change anything, but if i remove that, then it complains about the same entity being tracked when i do mapper.map<Entity>(doman), which creates a new entity and EF begins tracking it, I solved it by fetching the entity in the repository and doing mapper.map(entity, domain) so it replaces the one i fetched, but in my command handler I already did the fetch of the entity and did an in place mapping to change its values, how can I avoid re fetching in my repository?
92 Replies
mg
mg2d ago
what's this in the context of? are you updating an entity?
y4hira
y4hiraOP2d ago
I am updating an entity, that has owned properties, if i did it the first way, then it wouldnt delete the old owned property I have a code that is working but i dont like it
mg
mg2d ago
how are you actually updating the entity? ctx.Update() by chance?
y4hira
y4hiraOP2d ago
This right now works: but see i do a call to get as tracking
No description
No description
No description
y4hira
y4hiraOP2d ago
ive tried setting the state to modified/deleted to the owned properties and it doesnt work
mg
mg2d ago
wait, so what exactly is the issue? if that works, i don't really see a problem (aside from using the repository pattern on top of EF, but we can talk about that later) generally the update pattern is fetch entity (as a tracking query), set entity properties, save changes oh i see, maybe
y4hira
y4hiraOP2d ago
the issue is that if i use the mapper to make a new entity, not like it is now then it doesnt apply any changes since it thinks its a new entity and begins to track it the issue is that i do a get in the command handler and another get in the repository
mg
mg2d ago
yeah i see that, two fetches with automapper and repo on top of EF this is all kind of a mess why are you (or by you, automapper) creating a new entity?
y4hira
y4hiraOP2d ago
automapper does create an entity if you only give it one argument mapper.map<entity>(another entity) will result in a new entity if you do mapper.map(entity1, entity2) it will override entity 1
mg
mg2d ago
post the code that you want to work
y4hira
y4hiraOP2d ago
one second why is this not a good idea? btw, should UnitOfWork be the way to do it?
mg
mg2d ago
yes, and an EF context is already a unit of work
y4hira
y4hiraOP2d ago
No description
y4hira
y4hiraOP2d ago
this doesnt even update primitive properties let alone owned ones because the mapper creates a new entity that EF starts to track also our arch is now, commands and queries, and those handlers have repositories that have the dbcontext access
mg
mg2d ago
i think you can make it work by something like
var entity1 = GetEntity();
mapper.Map(entity1, request);
SaveChanges()
var entity1 = GetEntity();
mapper.Map(entity1, request);
SaveChanges()
assuming mapper.Map sets the properties of entity1 from request
y4hira
y4hiraOP2d ago
yeah but that still does 2 gets one in the command handler one in the repository which is what i dont like its a small nitpick i know
mg
mg2d ago
shouldn't there not be any data access in the command handler?
y4hira
y4hiraOP2d ago
but i just wanted to know if it was possible
mg
mg2d ago
except through the repository
y4hira
y4hiraOP2d ago
handlers have repositories as properties
mg
mg2d ago
oh right
y4hira
y4hiraOP2d ago
No description
mg
mg2d ago
yeah this seems like an arch problem
y4hira
y4hiraOP2d ago
you wouldnt have a repository pattern? what would the handlers use?
mg
mg2d ago
because if EditPayeeAsync() takes a payee and not an entity that can already be tracked by EF, you're necessarily either going to create a new entity or fetch one
y4hira
y4hiraOP2d ago
yeah repos take domain models
mg
mg2d ago
so there's no way you can edit a payee without adding a new one to the change tracker
y4hira
y4hiraOP2d ago
i wont change it now but want to learn what would be a good approach with the repo pattern and unit of work with cqrs
mg
mg2d ago
just don't inject the db context into your handler and do the work there what a repository does here is take an EF context and restrict its API which is now causing you problems because you want to edit a payee without adhering to the repository's implementation (i.e., taking a domain model and either mapping or fetching) here's how my kind of update handler looks https://github.com/MarkusG/sst/blob/main/api/Sst.Api/Features/UpdateTransaction/UpdateTransactionCommand.cs ignore the category logic and it's just
var entity = ctx.Entities.FirstOrDefault();

entity.Prop1 = request.Prop1;
entity.Prop2 = request.Prop2;

ctx.SaveChanges()
var entity = ctx.Entities.FirstOrDefault();

entity.Prop1 = request.Prop1;
entity.Prop2 = request.Prop2;

ctx.SaveChanges()
y4hira
y4hiraOP2d ago
should handlers have access to the dbcontext?
mg
mg2d ago
yeah
y4hira
y4hiraOP2d ago
oh you use rawsql
mg
mg2d ago
for that query specifically
y4hira
y4hiraOP2d ago
oh okay also i worked on another project in next
mg
mg2d ago
because i want to avoid a race between getting the max and doing the insert
y4hira
y4hiraOP2d ago
that had an ef core copy where we had readonly repos and write only repos commands and queries would have those and also have domain and entities
mg
mg2d ago
yeah that seems like overkill to me typically people here recommend $vsa
y4hira
y4hiraOP2d ago
its the second time i use cqrs
mg
mg2d ago
the last link there is a good talk on it
y4hira
y4hiraOP2d ago
i like it because a lot of separation of concerns we used services before it was a mess like genericservice that had dbcontext in them
mg
mg2d ago
yeah i started with that as well then it caused me a bunch of pain
y4hira
y4hiraOP2d ago
so all the repositories would be gone and just add entire db context to the handlers but wouldnt then you have access to all dbsets in any handler
mg
mg2d ago
yeah
y4hira
y4hiraOP2d ago
is that safe?
mg
mg2d ago
define safe
y4hira
y4hiraOP2d ago
well youre right
mg
mg2d ago
it's necessary for a handler with cross cutting concerns
y4hira
y4hiraOP2d ago
thjat in any repo i also have all db sets available to me
mg
mg2d ago
yeah exactly it's just an extra layer of abstraction you don't need you have access to all repositories in your handler if you inject them no difference between
MyHandler(UserRepository users, PostRepository posts)
{
users.DoSomething();
posts.DoSomething();
}
MyHandler(UserRepository users, PostRepository posts)
{
users.DoSomething();
posts.DoSomething();
}
and
MyHandler(DbContext ctx)
{
ctx.Users.DoSomething();
ctx.Posts.DoSomething();
}
MyHandler(DbContext ctx)
{
ctx.Users.DoSomething();
ctx.Posts.DoSomething();
}
y4hira
y4hiraOP2d ago
youd do something like this
No description
y4hira
y4hiraOP2d ago
i would need to add a mapping profile between the command and the entity tho
mg
mg2d ago
i posted what i'd do
y4hira
y4hiraOP2d ago
but if i do a lot of gets of entities, where would i put that repeated code?
mg
mg2d ago
forget automapper, forget repositories fetch, update, save what repeated code? payees.FirstOrDefault(...)?
y4hira
y4hiraOP2d ago
the firstordefault yes
mg
mg2d ago
is there something wrong with repeating that
y4hira
y4hiraOP2d ago
you said no repositories tho, or i misunderstood you yeah i know that repeated code is okay, but repeated intention is not and the intention is to fetch an entity
mg
mg2d ago
there i'm just pointing out the lack of a difference between repos and no repos in the context of what the handlers "have access to"
y4hira
y4hiraOP2d ago
so to me, thats bad
mg
mg2d ago
but what do you gain by tossing that FirstOrDefault() call into a repository method and then calling that method instead you still call the same method and pass the same IDs
y4hira
y4hiraOP2d ago
not saying into a repository but some other place
mg
mg2d ago
you could make an extension method on DbSet<PayeeEntity> if you want
y4hira
y4hiraOP2d ago
yeah that could work because i tried messing up with ef core, state modified, state deleted, get entry properties, get entry proprerties as objects, and couldnt get it to work
mg
mg2d ago
you mean like messing with the change tracker yourself?
y4hira
y4hiraOP2d ago
No description
y4hira
y4hiraOP2d ago
youd do something like that? yeah i tried a lot, and nothing worked
mg
mg2d ago
no, i wouldn't
y4hira
y4hiraOP2d ago
but would it be like that if i were to use extension methods
mg
mg2d ago
i would just write FirstOrDefault(...) everywhere i need to get an entity
y4hira
y4hiraOP2d ago
i know you said you would repeat the code
mg
mg2d ago
i can't give you an opinionated answer on how to do something that i wouldn't do in the first place
y4hira
y4hiraOP2d ago
i understand but thanks for all the inputs i will just live with the fetching twice
mg
mg2d ago
sure thing
y4hira
y4hiraOP2d ago
gets kinda worse in here tho
No description
mg
mg2d ago
i started out with CA and repositories and services and all that stuff but it caused so much pain and now that i use VSA everything is just way simpler there's much less thinking about where X should live and what Y's responsibilities are
y4hira
y4hiraOP2d ago
i didnt start this project, was passed it down to it as tech lead so just gotta follow what it is, cannot change a lot of stuff
mg
mg2d ago
that's always fun godspeed
y4hira
y4hiraOP2d ago
some stuff i tried haha
No description
y4hira
y4hiraOP2d ago
thank you friend
mg
mg2d ago
sure thing!
y4hira
y4hiraOP2d ago
also uhm how would you handle applying filters?
mg
mg2d ago
.Where()
y4hira
y4hiraOP2d ago
yeah but now i got a bunch of ifs haha
y4hira
y4hiraOP2d ago
No description
mg
mg2d ago
that's big indeed but, if you're filtering on a lot of different things then it's not wrong might be able to get rid of the ifs by using .Where(x => filterProp == null || x.FilterProp == FilterProp)
y4hira
y4hiraOP2d ago
yeah it depends on a lot of stuff
mg
mg2d ago
then the Where() is just a Where(true) if you're not filtering by that thing
y4hira
y4hiraOP2d ago
var filters = new List<Expression<Func<PayeeEntity, bool>>> creating something like that would work? then executing the where of all the ones that arent null?
mg
mg2d ago
uhh you mean like
var filters = new List<...>();

if (filter1)
filters.Add(filter1Predicate);
// ...

foreach (var f in filters)
query = query.Where(f);
var filters = new List<...>();

if (filter1)
filters.Add(filter1Predicate);
// ...

foreach (var f in filters)
query = query.Where(f);
? you could pointless though imo you've still got all the if statements no matter what you do, you'll always have to check if you're filtering by each thing so just let the code be long
y4hira
y4hiraOP2d ago
yeah i gues it is
Unknown User
Unknown User23h ago
Message Not Public
Sign In & Join Server To View

Did you find this page helpful?