Skip to content
jayoungers edited this page Oct 12, 2018 · 3 revisions

The PatchMap.Mapper is the heart of the library: for a given entity, the Mapper should be defined once (usually statically), and Map will be called given the current PatchContext:

public class BlogPatchCommand
{
    private static readonly Mapper<BlogViewModel, Blog, BasePatchContext> mapper = new Mapper<BlogViewModel, Blog, BasePatchContext>();

    static BlogPatchCommand()
    {
        mapper.AddMap(vm => vm.Name, db => db.Name);
        mapper.AddMap(vm => vm.Url, db => db.Url);
    }

    public PatchCommandResult<BlogViewModel> Execute(int? blogId, List<PatchOperation> operations)
    {
        //Get or create the entity dbBlog

        var results = mapper.Map(operations, dbBlog, GenerateContext(isNew));

        //Return results
    }
}

Given a set of PatchOperations, Map will work the following way:

  1. Iterate through the collection of defined Maps looking for a corresponding PatchOperation. If one is not found, the map is skipped. This means the order of your defined Maps is important: in the example above you can expect the property Name to have already been updated (if applicable) when the Url map is run
  2. If a matching PatchOperation was found:
    1. If this is a JsonPatch and the value is not parsable (i.e. the field is an Int but 'ABC' was passed in), we stop processing the map here with a result of JsonPatchValueNotParsable.
    2. If the map has a Enabled hook defined, run that now to determine if we should proceed. If the result is true, continue
    3. If the map has a Converter hook defined and the value is not null, run that now
      1. If the conversion did not succeed, we stop processing the map here with a result of ValueConversionFailed and the reason for the failure.
    4. At this point, if the value is null (or in the case of string: IsNullOrEmpty), we determine if the map is Required.
      1. If the map has a Required hook, run that now.
      2. The mapper itself has a MapTargetIsRequired method which can be overwritten. By default, this will always return false. It's recommended to overwrite this with the logic of: Field is not nullable
      3. If the Required hook returned true, or there was no hook and MapTargetIsRequired returned true, we stop processing the map here with a result of Required.
    5. If this map has a target field:
      1. Determine if its a valid value based on the mapper's MapTargetValueIsValid method, which can be overwritten. By default this will always return true. It's recommended to overwrite this with logic based on your entity store meta-data (i.e. check for string max lengths, valid date ranges, etc).
      2. If MapTargetValueIsValid returned false, we stop processing the map here with a result of ValueIsNotValid and the reason for the failure.
      3. If it returned true:
        1. We update the target field with the new value.
        2. Determine if the value has changed based on the mapper's MapTargetHasChanged method, which can be overwritten. By default it will return a simple !Equals(oldValue, newValue) comparison: it's recommended to update this to Entity is new or !Equals(oldValue, newValue), so that all PostMap methods are executed when dealing with new entities, even if the value was not set.
    6. If the target field's value has changed, or if this map does not target a field (i.e. is a collection), run the map's PostMap hook if it has been defined.
  3. While iterating through the collection of maps, if we run across a CompoundMap, we run the above process for each of its maps. If any of those maps resulted in a change, we run the CompoundMaps PostMap method if defined.

The reason the Mapper's MapTargetHasChanged, MapTargetIsRequired, MapTargetValueIsValid hooks are over-writable is to prevent the library from being tied to any one specific implementation of object-relational mapping: each has their own way of handling table metadata (column definitions, etc). The examples section of this repository and wiki include the recommended ways to overwrite these method for both EF6 and EFCore.

Clone this wiki locally