1

I have two object of class Book. It is possible merge two objects overriding null values?

Book model:

public string Author {get; set}
public string Title {get; set}
public string EAN {get; set}

First Book data:

Book.Author -> "Adam Wróbel"
Book.Title -> "StackOverflow Book"
Book.EAN-> "777777777"

Second Book data:

Book.Author -> null
Book.Title -> "StackOverflow Book v2"
Book.EAN-> null

Is it possible to combine these two objects in such a way that I get the result like this?

Book.Author -> "Adam Wróbel" (data from first book)
Book.Title -> "StackOverflow Book v2" (data from second book)
Book.EAN-> "777777777" (data from first book)

P.S. I know that I can compare each of these fields by checking if statements are not null and assign to the other object. However, this seems to be a fairly limited way to solve this problem.

Adam Wróbel
  • 344
  • 6
  • 25
  • 1
    How do you expect this to behave if the second object contains properties that are not in the first, or are they guaranteed to always be the same type? – David L Jun 29 '21 at 19:47
  • @David L They are always this same type -> Book – Adam Wróbel Jun 29 '21 at 19:48
  • 1
    When both properties have a value, are you *always* going to want the values from the 2nd one (such as with the Title in your example)? – Broots Waymb Jun 29 '21 at 19:50
  • 1
    Does this answer your question? [merging two objects in C#](https://stackoverflow.com/questions/8702603/merging-two-objects-in-c-sharp) – Heretic Monkey Jun 29 '21 at 20:01

3 Answers3

2

You can try using Automapper and setup it to ignore the null values for selfmapping:

var config = new MapperConfiguration(cfg => cfg.CreateMap<Book, Book>()
    .ForAllMembers(opts => opts.Condition((src, dest, member) => member != null)));
var mapper = config.CreateMapper();
var target = new Book
{
    Author = "1",
    Title = "1",
    EAN = "1"
};
var source = new Book
{
    Title = "2"
};
var book = mapper.Map(source, target);
Console.WriteLine($"{book.Author}-{book.Title}-{book.EAN}"); // prints "1-2-1"
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
1

Sorry, there's nothing built into c# that would do this. Your options are:

  • Manually compare and assign each field
  • Use reflection to automatically compare and assign each field
  • Find a library that does it for you

I'm sorry, but I don't know any libraries that could do this. Maybe Automapper?

Vilx-
  • 104,512
  • 87
  • 279
  • 422
1

The best way is to simply write manual mappers. But if you want something that will perform this for you in every simple POCO case where no constructors are involved, the following will use reflection to:

  • create a new output object
  • map the override prop if the value is not null
  • map the base prop if the override prop value is null
  • and return the cloned object
public static class Merger
{
    public static T CloneAndMerge<T>(T baseObject, T overrideObject) where T : new()
    {
        var t = typeof(T);
        var publicProperties = t.GetProperties();
        
        var output = new T();
        
        foreach (var propInfo in publicProperties)
        {
            var overrideValue = propInfo.GetValue(overrideObject);
            var defaultValue = !propInfo.PropertyType.IsValueType 
                ? null 
                : Activator.CreateInstance(propInfo.PropertyType);
            if (overrideValue == defaultValue)
            {
                propInfo.SetValue(output, propInfo.GetValue(baseObject));   
            }
            else 
            {
                propInfo.SetValue(output, overrideValue);
            }
        }
        
        return output;
    }
}

Please note, there is no optimization or caching here. I wouldn't recommend it for production code, but it is a simple way to accomplish what you are looking for.

David L
  • 32,885
  • 8
  • 62
  • 93
  • 1
    Also note that value types (`int`, `decimal`, `float`, `Guid`, `DateTime`, `struct`s, etc.) will never be null. – Vilx- Jun 29 '21 at 21:03
  • 1
    @Vilx- absolutely, although additional checks could be expanded for struct props. – David L Jun 29 '21 at 21:23
  • 1
    True, true. This could get really hairy really fast though. You should also check for fields, not only properties. Avoid read-only properties. Not sure about event backing fields. You might want a custom attribute to mark some properties as "no-copy". Another attribute to mark classes/properties as "deep-clone". Etc. – Vilx- Jun 29 '21 at 23:08
  • @DavidL it is a great solution. However, I have one more question about your proposal. The if loop checks the condition to see if overrideValue is null. However, in the case of an int type property, 0 can also be an empty property. I tried to add an additional check in the if statement `if (overrideValue == null) || (int)value == 0) `, but although overrideValue is 0 it is not detected in the function condition. – Adam Wróbel Jun 30 '21 at 07:38
  • 1
    @AdamWróbel you can add instance generation for struct types, which I added to my answer. Note however that this is again something that is unoptimized...you'd want to cache the result. – David L Jul 01 '21 at 21:23