Skip to content

Partial Updates

Leonardo Porro edited this page May 1, 2023 · 16 revisions

Unlike JavaScript, C# does not have an undefined value, so that, when deserializing content from a request, the values that are not present in source Json, are set to default() by the framework.

That could be a problem if default is a valid value for the business logic!

Clr .NET primitives, such as boolean, int, etc. can be turned into Nullable<>, so, for example if int x = 0 (zero) is a valid value, then int? x = null can be safely used as undefined.

For associations, null means removing the reference, even deleting the child entity if cascade is set. So using null as undefined is not an option.

Also, drawbacks of using nullables is that the value may not be optional in the database, but only for the update operation that is being performed. So that there is a risk of having invalid values stored, an a complexity of checking for null values everywhere when no null value is expected. (i.e. int User.Age is not optional, but in an update operation you don't want to change it, so you send null)

Here we want to analyze some solutions to the undefined value problem.

Specific DTOs

This library was created with the goal of have as many DTOs as needed without having to worry about mapping them to entities. Stop using Entities as DTOs and define a DTO adding only the fields that are going to be updated may help reducing the undefined value problem.

An issue here is that it doesn't solve the problem of optional properties, that may be send or not in the request.

IPatch & Patch Library

IPatch interface defines the IsSet(propName) method which is a dirty check automatically evaluated by the mapper. When a mapping type implements IPatch, the mapper will call IsSet() for each property name before attempt to map the property.

Manually implementing IPatch on each DTO is tedious and adds a lot of boilerplate to the models, so that the PatchTypes library provides a proxy builder that can create a new type derived from your model and automatically implement IPatch.

There is also a JsonConverter that can be added to a JsonSerializer for automatically create proxy types instead of original types when deserializing json, so that, the existing properties in Json will be tracked as IsSet = true, while the omited ones will be set as false. By integrating this converter to ASP.NET Mvc serializer, an IPatch version of the request body can be obtained and directly mapped to entities.

This test shows an example of an IPatch mapping operation.

This sample API shows how a request body is deserialized as an IPatch proxy class and its properties can be check for undefined values.

NOTE: When using the serializer or the patch factory, properties must be marked as virtual.

Optional<T> (HotChocolate only)

HotChocolate is a framework that provides GraphQL servers, clients and tools for the .NET ecosystem. This library defines a type called Optional<T>, that is a kind of nullable but works for reference values too.

In order to support Optionals, install Detached.Mappers.HotChocolate nuget package and add WithHotChocolate() to the mapper initialization:

services.AddDbContext<MainDbContext>(cfg =>
{
   cfg.UseMapper(mapperOptions => {
      
      mapperOptions.Default(profileOptions => {
         profileOptions.WithHotChocolate();
      });
      ...
   });
});

Big thanks to furier for the original idea and the first implementation.