2

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.

Roman Suska
  • 527
  • 2
  • 7
  • 21
  • You could use reflection... – Andrei Tătar Feb 09 '21 at 08:37
  • Do you want the possibility of *clearing* a property as part of the update? If so, how would you specify that? – Lasse V. Karlsen Feb 09 '21 at 08:38
  • It would be simpler to ship all the properties all the time even if they don't change value – Caius Jard Feb 09 '21 at 08:54
  • @LasseV.Karlsen no, I don't want to clear the properties. – Roman Suska Feb 09 '21 at 08:55
  • @CaiusJard, yes, but I'm not developing the client. The client will send me only the properties, that should be updated. – Roman Suska Feb 09 '21 at 08:57
  • @AndreiTătar, yes but reflection is cost heavy, and I want to make this module as light as possible. That's why I'm looking for another solution. – Roman Suska Feb 09 '21 at 08:59
  • You can write it as: `target.Property = source.Property ?? target.Property;` – Lasse V. Karlsen Feb 09 '21 at 09:04
  • You can't do this (except with a bunch of specific `ifs` or coalescing opeators) without reflection in a generic way C#. If you think reflection cost is too high, I'd consider other less statically typed languages... but languages that allow this will more likely use some kind of reflection internally and will probably be slower anyway (that is, if you don't want to mangle with pointers and in-memory data structures, which I bet you don't :-) ) – Jcl Feb 09 '21 at 09:07
  • check here. https://stackoverflow.com/questions/56485596/c-sharp-shorthand-for-if-not-null-then-assign-value – Aric Lam Feb 09 '21 at 09:15
  • Does this answer your question? [c# shorthand for if not null then assign value](https://stackoverflow.com/questions/56485596/c-sharp-shorthand-for-if-not-null-then-assign-value) – Orace Feb 09 '21 at 09:21

5 Answers5

3

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"
    }
}
Jonas Høgh
  • 10,358
  • 1
  • 26
  • 46
  • Thank you, this is a solution that suits my needs – Roman Suska Feb 25 '21 at 10:24
  • This doesn't quite work if you have fields which can't be null. E.g. `bool` or enums. Even if you make them null (e.g. `bool?`) `AutoMapper` overwrites with the default value. The reflection solution doesn't have this issue. – wilmol Apr 27 '23 at 06:09
2

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

DbEntity.PropertyA = DtoEntity.PropertyA ?? DbEntity.PropertyA;
Orace
  • 7,822
  • 30
  • 45
1

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)

Jcl
  • 27,696
  • 5
  • 61
  • 92
  • 1
    That's a one time hit with AM, the execution plan is [compiled](https://docs.automapper.org/en/latest/Understanding-your-mapping.html). – Lucian Bargaoanu Feb 09 '21 at 09:48
  • @LucianBargaoanu yes, I know, was talking about "not using reflection" since the OP specifically said he didn't want to. I'd definitely recommend Automapper for this task (but that was already in other answer) – Jcl Feb 09 '21 at 10:19
0

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

Caius Jard
  • 72,509
  • 5
  • 49
  • 80
0

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);
Michael Mairegger
  • 6,833
  • 28
  • 41