AutoMapper throws exception when projecting to nullable enum - c#

In my project I have a Linq queryable (actually an EF6 collection) that I need to convert to a collection of data transfer objects. I'm using AutoMapper throughout the project, especially for its ability to do type projection thereby reducing the amount of SQL generated by the Linq query.
But I've got a small problem with one of my DTO classes. The associated database column contains a nullable string, which I wish to map to a nullable enum. But at runtime an exception is thrown
No coercion operator is defined between types 'System.String' and 'System.Nullable`1[AutomapperEnumTest.Method]'.
Here's a complete test program that demonstrates the problem: (see .Net Fiddle)
using System;
using System.Linq;
using AutoMapper;
using AutoMapper.QueryableExtensions;
namespace AutomapperEnumTest
{
public class Program
{
public static void Main()
{
Configure();
var src = new SourceType[] { new SourceType { Name = "Rob", MethodName = "AUTO" } };
var results = src.AsQueryable()
.ProjectTo<DestType>();
foreach(var item in results)
{
Console.WriteLine(String.Format("DestType Name={0} Method={1}", item.Name, item.Method));
}
Console.WriteLine("Done");
}
private static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<string, Method?>().ProjectUsing(src => src == "MANUAL" ? Method.MANUAL : Method.AUTO);
cfg.CreateMap<SourceType, DestType>()
.ForMember(dest => dest.Method, opt => opt.MapFrom(src => src.MethodName));
});
}
}
public enum Method
{
MANUAL=0,
AUTO=1
}
public class DestType
{
public string Name {get; set; }
public Method? Method {get; set; }
}
public class SourceType
{
public string Name {get; set; }
public string MethodName {get; set; }
}
}
Now if I change the property from Method? to Method it works fine (see this modification in .Net Fiddle). But I don't want to do this, so my question is how do I inform Linq/AutoMapper that it should use my ProjectUsing for the nullable enum?
Many thanks.

The same happens in the latest AutoMapper v5.2.0.
After looking at the source code, I think it is a bug inside the ExpressionBuilder class because for some unknown reason NullableExpressionBinder is given higher priority than CustomProjectionExpressionBinder (and others), so basically when you map non nullable type to nullable, all the custom mappings are ignored.
I would suggest you to report it on their repository. Until then, I could suggest you the following workaround (hack). Include the following custom class in your project:
using System.Linq.Expressions;
using System.Reflection;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using AutoMapper.QueryableExtensions.Impl;
class NullableExpressionBinderEx : IExpressionBinder
{
public static void Install()
{
var bindersField = typeof(ExpressionBuilder).GetField("Binders", BindingFlags.NonPublic | BindingFlags.Static);
var binders = (IExpressionBinder[])bindersField.GetValue(null);
binders[0] = new NullableExpressionBinderEx();
}
IExpressionBinder baseBinder = new NullableExpressionBinder();
private NullableExpressionBinderEx() { }
public bool IsMatch(PropertyMap propertyMap, TypeMap propertyTypeMap, ExpressionResolutionResult result)
{
if (propertyTypeMap != null && propertyTypeMap.CustomProjection != null)
return false;
return baseBinder.IsMatch(propertyMap, propertyTypeMap, result);
}
public MemberAssignment Build(IConfigurationProvider configuration, PropertyMap propertyMap, TypeMap propertyTypeMap, ExpressionRequest request, ExpressionResolutionResult result, IDictionary<ExpressionRequest, int> typePairCount)
{
return baseBinder.Build(configuration, propertyMap, propertyTypeMap, request, result, typePairCount);
}
}
then add the following line to your Configure method:
NullableExpressionBinderEx.Install();
and the issue should be solved.

You could map MethodName to Method manually, unless I'm missing something in your question.
private static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<SourceType, DestType>()
.ForMember(dest => dest.Method, opt => opt.MapFrom(src =>
src.MethodName == "MANUAL" ? Method.MANUAL : Method.AUTO));
});
}

Related

EF Core InMemory array field IQueryable execution

I have a project with Microsoft.EntityFrameworkCore 5.0.17 on the runtime it is running under Npgsql.EntityFrameworkCore.PostgreSQL but now i'm writing a test over my class. I'm using an array field in my Entity:
// Enum of my array field
public enum MyEnum
{
One, Two, Three
}
// Entity
public class MyEntity
{
public int Id { get; set; }
public MyEnum[] Arr { get; set; }
}
So in my protected override void OnModelCreating(ModelBuilder builder) of my TestDatabaseContext I have to use a workaround to make it work:
var converter = new ValueConverter<MyEnum[], string>(
x => JsonConvert.SerializeObject(x),
x => JsonConvert.DeserializeObject<MyEnum[]>(x));
builder.Entity<MyEntity>().Property(x => x.Arr).HasConversion(converter);
But in my code I'm using such code that could not been translated during test:
public static IQueryable<MyEntity> WhereMyEnum(this IQueryable<MyEntity> queryable, params MyEnum[] args)
{
return queryable.Where(u => u.Arr.Any(x => args.Contains(x)))
}
Exception thrown:
The LINQ expression ... could not be translated. Either rewrite the query in a form that can be translated
Now I can only make such workaround on my code. But it would better to not change main logic because of Unit-tests...
if (!_context.Database.IsRelational())
queryable = queryable.Where(x => x.Arr.Contains(MyEnum.One));
else
queryable = queryable.WhereMyEnum(MyEnum.One);
I can also put that code to the local function and divide my implementation over database and memory. But maybe there is an another solution?

Linq to Entities could not be translated

I have this as my model (modified for obvious reasons)
public class Model
{
public int Id {get; set;}
public string Data {get; set;}
public TypeDomainValue ModelType {get; set;}
}
ModelType is known in the database as a string value only (no tables connected).
However if I want to filter on the value of TypeDomainValue in a linq statement
models.Where(c => c.ModelType.Value.Contains(searchString));
I get the error that the Linq expression cannot be translated.
I already tried using EF.Functions.Like which creates a similar error.
How can I get this to be translated properly as I do not want to load the entire table into memory.
Edit:
I use the following ValueConverter
public class ModelTypeDomainValueConverter : ValueConverter<ModelTypeDomainValue, string>
{
public ModelTypeDomainValueConverter([CanBeNull] ConverterMappingHints mappingHints = null) : base(ConvertToString(), ConvertToDomainValue(), mappingHints)
{
}
private static Expression<Func<ModelTypeDomainValue, string>> ConvertToString()
{
return x => x.Value;
}
private static Expression<Func<string, ModelTypeDomainValue>> ConvertToDomainValue()
{
return x => ModelTypeDomainValue.CreateByValue(x);
}
}
Which gets added with the following extension:
public static ModelBuilder UseValueConverter(this ModelBuilder modelBuilder, ValueConverter converter)
{
var type = converter.ModelClrType;
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == type);
foreach (var property in properties)
{
modelBuilder.Entity(entityType.Name).Property(property.Name).HasConversion(converter);
}
}
return modelBuilder;
}
Make ur life easy and Use owned properties in Entity framework core.
bcs TypeDomainValue smells like that one.
it has not its own table.
it don't have any primary key.
it resides in same table as Mode
so its own property.
I m not saying own properties can not have those but typically they are like them
and we use them as Value objects

How to use expression to assign property in LINQ Select new

I have the following situation in a generic class:
IQueryable<TQuote> query = GetQuotes(ctx)
where ctx is a database context and GetQuotes returns DbSet<TQuote>.
Then somewhere down the code the query is executed. Its simplified form is as follows:
var list = await query
.Select(v => new
{
v.Id,
TimeZone = v.Property != null ? (int?)v.Property.TimeZone : null
// and about 10 more simple properties like v.Id above
}
.ToListAsync();
where Property is a navigation property (to another table) and TimeZone is just a regular database column / property of type int? in C#.
Everything worked until I tried to use that generic class with the entity, which does not have a navigation property Property. So, I would like to replace the "hard coded" expression v.Property != null ? (int?)v.Property.TimeZone : null by an abstract member of the class and then override it differently for different TQuote types.
I tried something along this signature:
protected abstract Expression<Func<TQuote, int?>> GetTimeZone();
but then if I use it in LINQ (either directly or assigning to some variable first), then signature of TimeZone also changes to Expression<...> and if I try to ...Compile().Invoke(v), then LINQ complains that LINQ to entities does not support that.
I saw this answer: Create a Dynamic Linq to EF Expression to Select IQueryable into new class and assign properties However, it builds the whole selector by hands and given that I have total of 16 properties that would be a pain on the neck to create and maintain. So, I wonder if I can do something about this TimeZone only but leave the rest in a simple LINQ form as above.
Is it possible and if yes, then how exactly?
Try nuget LINQKit, https://github.com/scottksmith95/LINQKit.
Below is my attempt.
LinqKit provides the AsExpandable() and Invoke(v) methods.
I made up properties like Area.TimeZone and Region.RegionalTimeZone.
using LinqKit;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
class QuoteResult
{
public int Id { get; set; }
public int? TimeZone { get; set; }
public string Note { get; set; }
}
abstract class QuoteHelper<TQuote> where TQuote : Quote
{
protected abstract Expression<Func<TQuote, int?>> GetTimeZone { get; }
public IEnumerable<QuoteResult> GetQuoteResults(
EfExpressionPropertyDbContext ctx)
{
IQueryable<TQuote> query = GetQuotes(ctx);
var getTimeZone = GetTimeZone;
var list = query
.AsExpandable()
.Select(v => new QuoteResult
{
Id = v.Id,
TimeZone = getTimeZone.Invoke(v),
Note = v.Note
// and about 10 more simple properties like v.Id above
})
.ToList();
return list;
}
public IQueryable<TQuote> GetQuotes(
EfExpressionPropertyDbContext ctx)
{
return ctx.Set<TQuote>();
}
}
class CommonQuoteHelper : QuoteHelper<CommonQuote>
{
protected override Expression<Func<CommonQuote, int?>> GetTimeZone
{
get { return q => q.Area != null ? (int?)q.Area.TimeZone : null; }
}
}
class PeculiarQuoteHelper : QuoteHelper<PeculiarQuote>
{
protected override Expression<Func<PeculiarQuote, int?>> GetTimeZone
{
get { return q => q.Region != null ? (int?)q.Region.RegionalTimeZone : null; }
}
}

How to configure Automapper to automatically ignore properties with ReadOnly attribute?

Context:
Let's say I have the following "destination" class:
public class Destination
{
public String WritableProperty { get; set; }
public String ReadOnlyProperty { get; set; }
}
and a "source" class with the ReadOnly attribute on one of it's properties:
public class Source
{
public String WritableProperty { get; set; }
[ReadOnly(true)]
public String ReadOnlyProperty { get; set; }
}
It's obvious, but to be clear: I am going to map from Source class to Destination class in the following way:
Mapper.Map(source, destination);
Problem:
What are the ways to configure Automapper to automatically ignore property with ReadOnly(true) attribute?
Constraints:
I use Automapper's Profile classes for configuration. I don't want to dirty up classes with Automapper-specific attributes. I don't want to configure Automapper for every single read-only property and cause a lot of duplication by this way.
Possible (but not suited) solutions:
1) Add attribute IgnoreMap to the property:
[ReadOnly(true)]
[IgnoreMap]
public String ReadOnlyProperty { get; set; }
I don't want to dirty up classes with automapper-specific attributes and make it dependent from it. Also I don't want to add additional attribute along with ReadOnly attribute.
2) Configure Automapper to ignore the property:
CreateMap<Source, Destination>()
.ForSourceMember(src => src.ReadOnlyProperty, opt => opt.Ignore())
It is not a way because it forces me to do that for every single property everywhere and also causes a lot of duplication.
Write Extension Method as shown below:
public static class IgnoreReadOnlyExtensions
{
public static IMappingExpression<TSource, TDestination> IgnoreReadOnly<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
foreach (var property in sourceType.GetProperties())
{
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(sourceType)[property.Name];
ReadOnlyAttribute attribute = (ReadOnlyAttribute) descriptor.Attributes[typeof(ReadOnlyAttribute)];
if(attribute.IsReadOnly == true)
expression.ForMember(property.Name, opt => opt.Ignore());
}
return expression;
}
}
To call extension method:
Mapper.CreateMap<ViewModel, DomainModel>().IgnoreReadOnly();
Now you could also use ForAllPropertyMaps to disable it globally:
configure.ForAllPropertyMaps(map =>
map.SourceMember.GetCustomAttributes().OfType<ReadOnlyAttribute>().Any(x => x.IsReadOnly),
(map, configuration) =>
{
configuration.Ignore();
});
If you wanted to only map properties that have a certain attribute, in my case the [DataMember] attribute, I wrote a method based on the excellent reply above to handle this for both the source and destination:
public static class ClaimMappingExtensions
{
public static IMappingExpression<TSource, TDestination> IgnoreAllButMembersWithDataMemberAttribute<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
foreach (var property in sourceType.GetProperties())
{
var descriptor = TypeDescriptor.GetProperties(sourceType)[property.Name];
var hasDataMemberAttribute = descriptor.Attributes.OfType<DataMemberAttribute>().Any();
if (!hasDataMemberAttribute)
expression.ForSourceMember(property.Name, opt => opt.Ignore());
}
foreach (var property in destinationType.GetProperties())
{
var descriptor = TypeDescriptor.GetProperties(destinationType)[property.Name];
var hasDataMemberAttribute = descriptor.Attributes.OfType<DataMemberAttribute>().Any();
if (!hasDataMemberAttribute)
expression.ForMember(property.Name, opt => opt.Ignore());
}
return expression;
}
}
It will be called as the other method did:
Mapper.CreateMap<ViewModel,DomainModel>().IgnoreAllButMembersWithDataMemberAttribute();

AutoMapper auto create createMap

I have a services that is calling another services. Both of the services are using "the same classes". The classes are named same and have the same properties but has different namespace so I need to use AutoMapper to map from one of the type to the other type.
No it's pretty simple since all I have to do is the CreateMap<>, but the problem is that we have around hundreds of classes that I manually needs to write the CreateMap<> from, and it's works wired to me. Isn't there any Auto CreateMap function. So if I say CreateMap() then AutoMapper workes thru Organisation and finds all classes and automatically does the CreateMap for these Classes and it's subclasses etc etc…
Hope for a simple solution, or I guess some reflection can fix it...
Just set CreateMissingTypeMaps to true in the options:
var dto = Mapper.Map<FooDTO>
(foo, opts => opts.CreateMissingTypeMaps = true);
If you need to use it often, store the lambda in a delegate field:
static readonly Action<IMappingOperationOptions> _mapperOptions =
opts => opts.CreateMissingTypeMaps = true;
...
var dto = Mapper.Map<FooDTO>(foo, _mapperOptions);
UPDATE:
The approach described above no longer works in recent versions of AutoMapper.
Instead, you should create a mapper configuration with CreateMissingTypeMaps set to true and create a mapper instance from this configuration:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMissingTypeMaps = true;
// other configurations
});
var mapper = config.CreateMapper();
If you want to keep using the old static API (no longer recommended), you can also do this:
Mapper.Initialize(cfg =>
{
cfg.CreateMissingTypeMaps = true;
// other configurations
});
UPDATE 2 - Automapper 9 and later:
Starting from Automapper version 9.0, the CreateMissingTypeMaps API was removed. Automapper documentation now suggests to explicitly configure maps, manually or using reflection.
https://docs.automapper.org/en/stable/9.0-Upgrade-Guide.html#automapper-no-longer-creates-maps-automatically-createmissingtypemaps-and-conventions
CreateMissingTypeMaps can be set within your profile. It's however recommended to explicitly use CreateMap for each mapping and call AssertConfigurationIsValid in your unit tests for each profile to prevent silent errors.
public class MyProfile : Profile {
CreateMissingTypeMaps = true;
// Mappings...
}
AutoMapper has a DynamicMap method which you might be able to use: here's an example unit test illustrating it.
[TestClass]
public class AutoMapper_Example
{
[TestMethod]
public void AutoMapper_DynamicMap()
{
Source source = new Source {Id = 1, Name = "Mr FooBar"};
Target target = Mapper.DynamicMap<Target>(source);
Assert.AreEqual(1, target.Id);
Assert.AreEqual("Mr FooBar", target.Name);
}
private class Target
{
public int Id { get; set; }
public string Name { get; set; }
}
private class Source
{
public int Id { get; set; }
public string Name { get; set; }
}
}
Set CreateMissingTypeMaps option to true. This is package AutoMapper.Extensions.Microsoft.DependencyInjection's example for ASP.NET Core:
public class Startup {
//...
public void ConfigureServices(IServiceCollection services) {
//...
services.AddAutoMapper(cfg => { cfg.CreateMissingTypeMaps = true; });
//...
}
//...
}
In case someone is still interested in this topic, I've created a NuGet package that gives the automatic mapping functionality since AutoMapper removed it in a certain version.
It's available under wakiter.AutoMapper.Extensions name.
To use it, invoke the CreateAutoMap extension method and it'll do the work for you.
Today I needed this in some generic code as well. I tried something like this:
private static IMapper CreateMapper<T1, T2>()
{
return new MapperConfiguration(cfg => FillMapperConfig(cfg, typeof(T1), typeof(T2)))
.CreateMapper();
}
private static void FillMapperConfig(IMapperConfigurationExpression cfg, Type T1, Type T2)
{
if (T1 == T2)
{
return;
}
cfg.CreateMap(T1, T2);
foreach (PropertyInfo propertyInfo in T1.GetProperties())
{
PropertyInfo correspondingProperty =
T2.GetProperties()
.FirstOrDefault(p =>
p.Name == propertyInfo.Name);
if (correspondingProperty != null)
{
if (propertyInfo.PropertyType.IsGenericType &&
correspondingProperty.PropertyType.IsGenericType)
{
FillMapperConfig(
cfg,
propertyInfo.PropertyType.GetGenericArguments()[0],
correspondingProperty.PropertyType.GetGenericArguments()[0]);
}
else if (propertyInfo.PropertyType.IsClass &&
correspondingProperty.PropertyType.IsClass)
{
FillMapperConfig(
cfg,
propertyInfo.PropertyType,
correspondingProperty.PropertyType);
}
}
}
}
Then I can do something like this:
IMapper mapper = CreateMapper<ClassA, ClassB>();
Which creates a map from ClassA to ClassB with all sub properties of ClassA and ClassB if they have the same name and recursively for sub sub properties.
Example:
public class ClassA {
public int IntProperty { get; set; }
public ClassASubProperty SubProperty { get; set; }
public List<ClassAListItem> ListItems { get; set; }
}
public class ClassB {
public int IntProperty { get; set; }
public ClassBSubProperty SubProperty { get; set; }
public List<ClassBListItem> ListItems { get; set; }
}
This should result in the IMapper equivalent:
new MapperConfiguration(cfg => {
cfg.CreateMap<ClassA, ClassB>();
cfg.CreateMap<ClassASubProperty, ClassBSubProperty>();
cfg.CreateMap<ClassAListItem, ClassBListItem>()
}).CreateMapper();

Categories

Resources