2

I am trying to do some conditional mapping, and all the documentation and questions I have read through doesn't seem to cover this particular conditional. I was hoping someone here would have the experience or know how on the best approach to this.

I am mapping an object that has two properties. However, I do not want to map EITHER property, if a specific property is of a value. To visualize this:

foreach(var object in objectB) {
    If (object.propertyA == "SomeValue")
        continue;
    else
        Mapper.Map<ObjectA>(object);
}

however, I want the AutoMapper equivalent of the above statement. So something more like:

cfg.CreateMap<ObjectB, ObjectA>()
    .ForMember(dest => dest.PropertyA, m => m.Condition(source => source.PropertyA != "SomeValue"))
    .ForMember(dest => dest.PropertyB, m => m.Condition(source => source.PropertyA != "SomeVAlue" ? source.PropertyB : ignore))

But the above version obviously does not work. Thank you in advance for your assistance.

5
  • I wouldn't write this into the mappings, I would create a class that contains an AutoMapper, and also contains a method that checks your ignore case before creating the new ObjectA. If you don't want to create a mapping while ObjectB has a certain property value, check for this value, and then creates an ObjectA or return null. Don't call AutoMapper directly. Commented Jul 31, 2018 at 15:07
  • To be honest, I think that Automapper is kind of pure evil and you should not use it. The main reason here - you're losing benefits of strong types. Simplest example - you can rename a property in one model, but not rename it in another. Your code still will build, but you will have null in runtime, and eventually get NRE or empty field instead of expected data. Commented Jul 31, 2018 at 16:21
  • That's why AssertConfigurationIsValid exists. Commented Jul 31, 2018 at 16:45
  • @JMad I understand where you are coming from, but that would defeat the purpose of using AutoMapper in this case... I'd might as well just manually map it since it's two properties. Commented Jul 31, 2018 at 16:52
  • @DenysProdan That is only true if you don't use what Lucian Bargaoanu mentioned Commented Jul 31, 2018 at 16:53

1 Answer 1

7

Could be achieved using conditional mapping, see Documentation(http://docs.automapper.org/en/latest/Conditional-mapping.html) for details. To cover arrays filtering case I created a custom type converter which is a little bit tricky (see http://docs.automapper.org/en/stable/Custom-type-converters.html). The updated example is below

using AutoMapper;
using System;
using System.Collections.Generic;

namespace ConsoleAppTest2
{
    class Program
    {
        static void Main(string[] args)
        {

            Mapper.Initialize(cfg => {
                //Using specific type converter for specific arrays
                cfg.CreateMap<Foo[], FooDto[]>().ConvertUsing(new ArrayFilterTypeConverter<Foo[], FooDto[], Foo, FooDto>(
                                                                                                               (src, dest) => (src.Age > 0)
                                                                                                            ));

                cfg.CreateMap<Foo, FooDto>()
                    .ForMember(dest => dest.Age, opt => opt.Condition(src => (src.Age >= 0)))
                    .ForMember(dest => dest.CurrentAddress, opt =>
                                                                {
                                                                    opt.Condition(src => (src.Age >= 0));
                                                                    opt.MapFrom(src => src.Address);
                                                                });

            });

            var foo = new Foo() { Name = "Name", Address = "Address", Age = -1 };
            var fooDTO = new FooDto();


            var fooArray = new Foo[] {
                    new Foo() { Name = "Name1", Address = "Address1", Age = -1 },
                    new Foo() { Name = "Name2", Address = "Address2", Age = 1 },
                    new Foo() { Name = "Name3", Address = "Address3", Age = 1 }
            };

            var fooDTOArray =  Mapper.Map<Foo[], FooDto[]>(fooArray); //get 2 elements instead of 3

            Mapper.Map(foo, fooDTO);
            //The result is we skipped Address and Age properties becase Age is negative

            Console.ReadLine();
        }
    }

    public class ArrayFilterTypeConverter<TSourceArray, TDestArray, TSource, TDest> : ITypeConverter<TSourceArray, TDestArray>
    {
        private Func<TSource, TDest, bool> filter;

        public ArrayFilterTypeConverter(Func<TSource, TDest, bool> filter)
        {
            this.filter = filter;
        }

        public TDestArray Convert(TSourceArray source, TDestArray destination, ResolutionContext context)
        {
            var sourceArray = source as TSource[];
            List<TDest> destList = new List<TDest>();

            var typeMap = context.ConfigurationProvider.ResolveTypeMap(typeof(TSource), typeof(TDest));

            foreach (var src in sourceArray)
            {
                var dest = context.Mapper.Map<TSource, TDest>(src);

                if (filter(src, dest))
                    destList.Add(dest);
            }

            // Little hack to cast array to TDestArray
            var result = (TDestArray)(object)destList.ToArray();
            return result;
        }
    }

    internal class FooDto
    {
        public string Name { get; set; }
        public string CurrentAddress { get; set; }
        public int Age { get; set; }

    }

    internal class Foo
    {
        public string Name { get; set; }
        public string Address { get; set; }

        public int Age { get; set; }
    }
}
Sign up to request clarification or add additional context in comments.

6 Comments

I had thought about this too, but then comes the secondary problem of objectB's property name not lining up with ObjectA so how would that be handled? Using the above example, If address was Address in ObjectA, but is CurrentAddress in ObjectB, how would this same conditional apply?
You can combine multiple options to construct whatever you need. Modified example to show the approach.
So trying this, it appears to work perfectly. However, it does not work in the case of a list. Example: if I pass in 4 entries with 1 designed to be parsed out (due to the value), I still end with 4 entries; however, that parsed entry is an empty object. Is there something I am missing?
Ok, you made my day today ;). It was a real challenge and sometimes I was thinking it's impossible but nothing impossible for us, right? Ok, moving to the solution, I'm not sure filtering logic should be part of a mapping and in addition to that, the code is a little bit overcomplicated because I couldn't find a way to make it simpler (which doesn't mean it couldn't be simplified) but it does the job. I updated the example above to cover case with arrays.
@Brandon it's clear Sergey's put in work on this answer, since you've found this useful Upvote, and if it resolve your question, then mark the question as resolved.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.