Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automapper projection fails with nullable properties

Tags:

c#

automapper

Automapper version: 6.0.2

I have two classes InternalEntity as my data source and PublicEntity exposed as API. I'm trying to access InternalEntity via PublicEntity projection using UseAsDataSource method.

The thing is that internal bool property mapped to public Nullable<bool> property.

It fails when I'm using this property in projection e.g. calling OrderBy on that. Please see sample below for details.

Can I set up projection rule for bool? -> bool somehow?

Here is code sample:

using AutoMapper;
using AutoMapper.QueryableExtensions;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sample
{
    internal class Program
    {
        public class PublicEntity
        {
            public string PublicName { get; set; }
            public bool? Active { get; set; }
        }
        public class InternalEntity
        {
            public string InternalName { get; set; }
            public bool Active { get; set; }
        }

        private static IMapper mapper;

        private static void SetupMapping()
        {
            var mc = new MapperConfiguration(cfg =>
            {
                cfg.CreateMap<InternalEntity, PublicEntity>()
                    .ForMember(dest => dest.PublicName, opt => opt.MapFrom(src => src.InternalName));

                // setup bool? -> bool projection somehow
            });

            mc.AssertConfigurationIsValid();
            mapper = mc.CreateMapper();
        }

        private static void Main(string[] args)
        {
            SetupMapping();

            try
            {
                IQueryable<PublicEntity> resultable = DataSource()
                    .UseAsDataSource(mapper)
                    .For<PublicEntity>()
                    .OrderBy(x => x.Active);

                resultable.ToList()
                    .ForEach(x => Console.WriteLine(x.PublicName));
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            Console.ReadLine();
        }


        private static IQueryable<InternalEntity> DataSource()
        {
            return new List<InternalEntity>()
            {
                new InternalEntity(){ InternalName = "Name 1", Active = true},
                new InternalEntity(){ InternalName = "Name 3", Active = true},
                new InternalEntity(){ InternalName = "Name 2", Active = false},
            }.AsQueryable();
        }
    }
}

Exception:

System.ArgumentException: Expression of type 'System.Linq.Expressions.Expression`1[System.Func`2[Sample.Program+InternalEntity,System.Boolean]]' cannot be used for parameter of type 'System.Linq.Expressions.Expression`1[System.Func`2[Sample.Program+InternalEntity,System.Nullable`1[System.Boolean]]]' of method 'System.Linq.IOrderedQueryable`1[Sample.Program+InternalEntity] OrderBy[InternalEntity,Nullable`1](System.Linq.IQueryable`1[Sample.Program+InternalEntity], System.Linq.Expressions.Expression`1[System.Func`2[Sample.Program+InternalEntity,System.Nullable`1[System.Boolean]]])'
   at System.Linq.Expressions.Expression.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arg, ParameterInfo pi)
   at System.Linq.Expressions.Expression.ValidateArgumentTypes(MethodBase method, ExpressionType nodeKind, ReadOnlyCollection`1& arguments)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
   at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.GetConvertedMethodCall(MethodCallExpression node)
   at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at AutoMapper.QueryableExtensions.Impl.SourceInjectedQueryProvider`2.ConvertDestinationExpressionToSourceExpression(Expression expression)
   at AutoMapper.QueryableExtensions.Impl.SourceInjectedQueryProvider`2.Execute[TResult](Expression expression)
   at AutoMapper.QueryableExtensions.Impl.SourceSourceInjectedQuery`2.GetEnumerator()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Sample.Program.Main(String[] args) in xxxx\Program.cs:line 48
like image 889
kletnoe Avatar asked Oct 13 '25 09:10

kletnoe


2 Answers

I managed to find solution myself. One or the other line will do nicely:

.ForMember(dest => dest.Active, opt => opt.MapFrom(src => (bool?)src.Active))

.ForMember(dest => dest.Active, opt => opt.MapFrom(src => new Nullable<bool>(src.Active)))

Those lines are semantically equivalent.

Note that those lines produce different expression trees, which may become important if you are going to parse expression trees.

like image 134
kletnoe Avatar answered Oct 14 '25 23:10

kletnoe


This error sounds logical - you try to convert a value which can hold "true", "false" or "no value" to another one which can only hold "true" or "false".

You can tell it to map from nullable Value:

var mc = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<InternalEntity, PublicEntity>()
        .ForMember(dest => dest.PublicName, opt => opt.MapFrom(src => src.InternalName))
        .ForMember(dest => dest.Active, opt => opt.MapFrom(src => src.Active.Value));
});

Note that it will throw an exception if source Active has no value (obviously).

If Active equal to null means "inactive", then you can just use ?? operator:

.ForMember(dest => dest.Active, opt => opt.MapFrom(src => src.Active.Value ?? false));

But why would you need a nullable at all then? :)

like image 20
Yeldar Kurmangaliyev Avatar answered Oct 14 '25 23:10

Yeldar Kurmangaliyev