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
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.
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? :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With