cyptus
cyptus

Reputation: 3416

ef core best practice to update complexe objects

We are leading into some issues with ef-core on sql databases in a web-api when trying to update complexe objects on the database provided by a client.

A detailed example: When receiving an object "Blog" with 1-n "Posts" from an client and trying to update this existing object on database, should we:

  1. Make sure the primary keys are set and just use dbContext.Update(blogFromClient)
  2. Load and track the blog while including the posts from database, then patch the changes from client onto this object and use SaveChanges()

When using approach (1) we got issues with:

  1. Existing posts for the existing blog on database are not deleted when the client does not post them any more, needing to manually figure them out and delete them

  2. Getting tracking issues ("is already been tracked") if dependencies of the blog (for example an "User" as "Creator") are already in ChangeTracker

  3. Cannot unit test our business logic without using a real DbContext while using a repository pattern (tracking errors do just not exist)

  4. While using a real DbContext with InMemoryDatabase for tests cannot rely on things like foreign-key exceptions or computed columns

when using approach (2):

  1. we can easily manage updated relations and keep an easy track of the object
  2. lead into performance penalty because of loading the object which we do not really need
  3. need to map many manual things as tools like AutoMapper cannot be used to automaticlly map objects with n-n relations while keeping a correct track by ef core (getting some primary key errors, as some objects are deleted from lists and are added again with the same primary key, which is not allowed as the primary key cannot be set on insert)
  4. n-n relations can be easily damaged by this as on database there could be n-n blog to post, while the post in blog does hold the same relation to its posts. if only one relation is (blog to post, but not post to blog - which is the same in sql) is posted and the other part is deleted from list, ef core will track this entry as "deleted".

in vanilla SQL we would manage this by

  1. deleting all existing relations for the blog to posts
  2. updating the post itself
  3. creating all new relations

in ef core we cannot write such statements like deleting of bulk relations without loading them before and then keeping detailed track on each relation.

Is there any best practice, how to handle an update of complexe objects with deep relations while getting the "new" data from a client?

Upvotes: 0

Views: 2528

Answers (1)

Chris Pratt
Chris Pratt

Reputation: 239440

The correct approach is #2: "Load and track the blog while including the posts from database, then patch the changes from client onto this object and use SaveChanges()".

As to your concerns:

lead into performance penalty because of loading the object which we do not really need

You are incorrect in assuming you don't need this. You do in fact need this because you absolutely shouldn't be posting every single property on every single entity and related entity, including things that should not be be changed like audit props and such. If you don't post every property, then you will end up nulling stuff out when you save. As such, the only correct path is to always load the full dataset from the database and then modify that via what was posted. Doing it any other way will cause problems and is totally and completely 100% wrong.

need to map many manual things as tools like AutoMapper cannot be used to automaticlly map objects with n-n relations while keeping a correct track by ef core

What you're describing here is a limitation of any automatic mapping. In order to map entity to entity in collections, the tool would have to somehow know what identifies each entity uniquely. That's usually going to be a PK, of course, but AutoMapper doesn't (and shouldn't) make assumptions about that. Instead, the default and naive behavior is to simply replace the collection on the destination with the collection on the source. To EF, though, that looks like you're deleting everything in the collection and then adding new items to the collection, which is the source of your issue.

There's two paths forward. First, you can simply ignore the collection props on the source, and then manually map these. You can still use AutoMapper for the mapping, but you'd simply need to iterate over each item in the collection individually matching it with the appropriate item that should map to it, based on your knowledge of what identifies the entity (i.e. the part AutoMapper doesn't know).

Second, there's actually an additional library for AutoMapper to make this easier: AutoMapper.Collection. The entire point of this library is to provide the ability to tell AutoMapper how to identify your entities, so that it can then map collections correctly. If you utilize this library and add the additional necessary configuration, then you can map your entities as normal without worrying about collections getting messed up.

Upvotes: 2

Related Questions