Reputation: 14495
I need to project integer values from the database to enum values using Entity Framework and AutoMapper. The problems seems to be that the columns can be nullable in some cases and non-nullable in other cases. If they are of a nullable type, I want to use a default value (first enum value) for nulls.
Here is a complete, minimal example I put together with my current approach. It is a console application with current nuget packages of Entity Framework and AutoMapper installed. It also needs a database (for database first) with a table like the following:
CREATE TABLE [dbo].[MyTable] (
[Id] INT PRIMARY KEY,
[EnumValue] INT NOT NULL,
[EnumValueNullable] INT
)
The code for the console application (C#):
public enum MyEnum
{
Value1 = 0,
Value2 = 1
}
public class MyTableModel
{
public int Id { get; set; }
public MyEnum EnumValue { get; set; }
public MyEnum EnumValueNullable { get; set; }
}
public class MyProfile : Profile
{
public MyProfile()
{
this.CreateMap<MyTable, MyTableModel>();
this.CreateMap<int, MyEnum>().ProjectUsing(x => (MyEnum)x);
this.CreateMap<int?, MyEnum>().ProjectUsing(x => x.HasValue ? (MyEnum)x.Value : MyEnum.Value1);
}
}
static void Main(string[] args)
{
var config = new MapperConfiguration(x => x.AddProfile(new MyProfile()));
var result = new MyDataEntities().MyTable.ProjectTo<MyTableModel>(config).ToList();
}
When executing this code, it tells me that HasValue
is not defined for type System.Int32
.
This is of course correct, but I assumed AutoMapper would pick the version specified for non-nullable integers.
Removing either maps (for int and int?) does not help, as well as removing them both or changing the order.
As a side note, I am migrating AutoMapper from version 3.3.1.0 in a larger project. It seems to have worked with both maps defined in that version.
Upvotes: 4
Views: 891
Reputation: 205849
There is (IMHO) a bug in the current AutoMapper MapperConfiguration class which is causing the specified mapping for Nullable<TSource> -> TDestination
to be used (overriding the already specified) for TSource -> TDestination
. It can easily be seen by executing the following code:
var config = new MapperConfiguration(x => x.AddProfile(new MyProfile()));
var m1 = config.ResolveTypeMap(typeof(int), typeof(MyEnum));
var m2 = config.ResolveTypeMap(typeof(int?), typeof(MyEnum));
Console.WriteLine(m1 == m2); // true
The resulting effect is the aforementioned runtime exception when AutoMapper is trying to map TSource
to TDestination
(in your case, int
to MyEnum
for EnumValue
property) using the specified expression (because x
is int
, not int?
as expected, hence has no HasValue
and Value
properties).
As a workaround (or general solution?) I would suggest defining only the map from int
to enum
, and using the AutoMapper Null substitution feature for the other part. Since currently null substitution is supported only at property map level, to make it easier I would encapsulate it in a helper method like this:
public static class AutoMapperExtensions
{
public static void NullSubstitute<TSource, TDestination>(this Profile profile, TSource nullSubstitute)
where TSource : struct
{
object value = nullSubstitute;
profile.ForAllPropertyMaps(
map => map.SourceType == typeof(TSource?) &&
map.DestinationPropertyType == typeof(TDestination),
(map, config) => config.NullSubstitute(value)
);
}
}
and use it like this:
public class MyProfile : Profile
{
public MyProfile()
{
this.CreateMap<MyTable, MyTableModel>();
this.CreateMap<int, MyEnum>().ProjectUsing(x => (MyEnum)x);
this.NullSubstitute<int, MyEnum>((int)MyEnum.Value1);
}
}
Upvotes: 4
Reputation: 58898
Alternatively you could try to do the mapping like this:
this.CreateMap<MyTable, MyTableModel>()
.ForMember(dest => dest.EnumValue, o => o.MapFrom(src => (MyEnum)src.EnumValue))
.ForMember(dest => dest.EnumValueNullable, o => o.MapFrom(src => src.EnumValueNullable != null ? src.EnumValueNullable.Value : MyEnum.Value1));
Upvotes: 1