Roman Suska
Roman Suska

Reputation: 537

How to update an object with non-NULL properties?

I am making an WebAPI endpoint which task is to update some properties of a database object. I send an object to the endpoint that contains only those properties that can be updated. If any of the properties is NULL, the program should omit this property during the update.

At the moment, all I can think of is to use the if statement to check if the following fields are non-NULL. So my updating class looks like this:

if (DtoEntity.PropertyA != null)
   DbEntity.PropertyA = DtoEntity.PropertyA;
if (DtoEntity.PropertyB != null)
   DbEntity.PropertyB = DtoEntity.PropertyB;

However, I do not like the design of this mapper. Is there any way to do such mapping more effectively?

-- EDIT I want to avoid reflection if it is possible in this scenario. I want to make this solution as light as possible.

Upvotes: 2

Views: 2263

Answers (5)

Jonas Høgh
Jonas Høgh

Reputation: 10874

This can be done with AutoMapper using a conditional mapping. If you don't want to specify this for every property of every type, you can use the ForAllMembers configuration to set it up for all members on a type:

using System;
using AutoMapper;

class Foo
{
    public string Prop1 { get;set; }
    public string Prop2 { get;set; }
}

class Bar {
    public string Prop1 { get;set; }
    public string Prop2 { get;set; }
}

public class Program
{
    
    public static void Main()
    {
        
        var configuration = new MapperConfiguration(
                cfg => cfg
                    .CreateMap<Foo,Bar>()
                    .ForAllMembers(x => x.Condition(
                      (src, dest, sourceValue) => sourceValue != null)));
        
        IMapper m = configuration.CreateMapper();
        
        var source = new Foo { Prop1 = null, Prop2 = "p2" };
            
        var dest = new Bar { Prop1 = "p3", Prop2 = "p4" };
            
        m.Map<Foo, Bar>(source, dest);
            
        // dest will now have Prop1 == "p3" and Prop2 == "p2"
    }
}

Upvotes: 3

Jcl
Jcl

Reputation: 28272

I know you said you didn't want to use reflection, however using reflection in C# it'd be as simple as (very non-optimized code):

public static void UpdateActualFromDto(object actual, object dto) {

    var dtoProps = dto.GetType().GetProperties();
    var actualProps = actual.GetType().GetProperties();
    var sameProperties = dtoProps.Where(x => actualProps.Select(a => a.Name).Contains(x.Name)).ToDictionary(x => x, x => actualProps.First(p => p.Name == x.Name));
    foreach(var (dtoProp, actualProp) in sameProperties) {
        var val = dtoProp.GetValue(dto);
        if(val != null) actualProp.SetValue(actual, val);
    }
}

And just for fun, I benchmarked it (again, this is hardly optimized, you could even have a cache of PropertyInfo and make it way faster):

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-9700K CPU 3.60GHz (Coffee Lake), 1 CPU, 8 logical and 8 physical cores
.NET Core SDK=5.0.102
  [Host]     : .NET Core 3.1.11 (CoreCLR 4.700.20.56602, CoreFX 4.700.20.56604), X64 RyuJIT  [AttachedDebugger]
  DefaultJob : .NET Core 3.1.11 (CoreCLR 4.700.20.56602, CoreFX 4.700.20.56604), X64 RyuJIT
Method Mean Error StdDev
CopyNonNull 899.7 ns 3.24 ns 2.71 ns

The benchmarked code is:


public class Dto {
    public string PropertyA { get; set; }
    public int? PropertyB { get; set; }
}

public class Actual {
    public string PropertyA { get; set; }
    public int PropertyB { get; set; }
}

public class CopyDtoBenchmark
{
    [Benchmark]
    public void CopyNonNull()
    {
        UpdateActualFromDto(new Actual { PropertyA = "bar", PropertyB = 5 }, new Dto { PropertyA = "foo" });
    }

    // UpdateActualFromDto method above
}

Again, no caching, and the benchmark is creating and allocating the objects (botht he actual and the dto).

Just for fun, I added some caching for matching dto/entity types, and times were reduced (for this particular benchmark that uses the same types all the time) to:

Method Mean Error StdDev
CopyNonNull 322.8 ns 1.28 ns 1.20 ns

Updated code:


private Dictionary<(Type, Type), Dictionary<PropertyInfo, PropertyInfo>> _typeMatchCache =
    new();


public void UpdateActualFromDto(object actual, object dto)
{
    var dtoType = dto.GetType();
    var actualType = actual.GetType();

    if (!_typeMatchCache.TryGetValue((dtoType, actualType), out var sameProperties))
    {
        var dtoProps = dtoType.GetProperties();
        var actualProps = actualType.GetProperties();
        sameProperties = dtoProps.Where(x => actualProps.Select(a => a.Name).Contains(x.Name)).ToDictionary(x => x, x => actualProps.First(p => p.Name == x.Name));
        _typeMatchCache.Add((dtoType, actualType), sameProperties);
    }
    foreach(var (dtoProp, actualProp) in sameProperties) {
        var val = dtoProp.GetValue(dto);
        if(val != null) actualProp.SetValue(actual, val);
    }
}

Again, this is a just for fun experiment, and if you were to use code like this you'd probably need better checks (check if types are the same, etc.), but I wouldn't discard reflection saying it's "cost heavy" without actually trying if the performance is good enough and whether the CPU cost of using it vs the cost of programming (and more importantly, maintaining, every time any of the involved classes changes) a non-generic solution is worth it.

By the way, I wouldn't recommend using this and I'd probably go for something like Automapper for it (but that WILL use reflection)

Upvotes: 1

Michael Mairegger
Michael Mairegger

Reputation: 7301

For this scenario you can use an auto mapper like https://github.com/AutoMapper/AutoMapper This library supports mapping dto object to the database model objects.

After configuring you can write the following code

var myDatabaseObject = mapper.Map<MyDatabaseObject>(objectFromWebRequest);

or vice versa

var myDtoObject = mapper.Map<MyDtoObject>(objectFromDatabase);

Upvotes: 0

Orace
Orace

Reputation: 8359

The null-coalescing operator ?? can be used to make the code lighter:

DbEntity.PropertyA = DtoEntity.PropertyA ?? DbEntity.PropertyA;

Upvotes: 2

Caius Jard
Caius Jard

Reputation: 74605

If you don't want to use reflection, maybe you can instead back your properties with a dictionary:

class DbEntity{

  public Dictionary<string, object> Props = new ...

  public string PropA
  {
    get => (string)Props[nameof(PropA)];
    set => Props[nameof(PropA)] = value;
  }
}

And the same for Dto

And then when copying values you could foreach:

foreach(var kvp in dbEntityInstance.Props.Where(x => x.Value != null))
  dtoInstance.Props[kvp.Key] = dbEntityInstance.Props[kvp.Key];

And same for dto

Upvotes: 0

Related Questions