Reputation: 1453
AutoMapper IQueryable Extension's Project().To<TViewModel>().SingleOrDefault()
throws this exception:
Cannot compare elements of type 'App.Domain.MyComplexType. Only primitive types, enumeration types and entity types are supported.
I have this model:
public class MyEntityType // this is an entity type on the dbContext
{
public int Id {get;set;
public MyComplexType MyComplexType {get;set;}
}
public class MyComplexType // this is a complex type
{
public decimal Property1 { get; set;}
public string Property2 { get;set;}
}
public class ViewModel
{
public int Id { get;set;}
public decimal MyComplexTypeProperty1 { get;set;}
}
I use AutoMapper to configure mapping from IQueryable<MyEntityType>
to ViewModel
:
Mapper.CreateMap<MyEntityType, MyComplexType>(); // I rely on AutoMapper's
//convention for flattening `source.MyComplexType.Property1` to `dest.MyComplexTypeProperty1'
Then I try to retrieve a single item like this:
var myItem = myContext.Where(x => x.Id == id).Project().To<ViewModel>().SingleOrDefault();
I get the above exception when SingleOrDefault()
is called, so apparently
I currently work around this by first calling SingleOrDefault()
and then doing the mapping, this works:
var myItem = Mapper.Map<ViewModel>(myContext.Find(id));
Other posts basically say that the error above arises when trying to compare a EF Complex Type with null, as, e.g., in a Where
clause, but that is apparently not the case here.
Upvotes: 8
Views: 1713
Reputation: 966
I was having this issue with EF 8
since it allowed using ComplexTypes
again.
The problem here is that ComplexTypes
in .Net 8 are not supposed to be null, so the solution is to avoid null value.
Also there was change in AutoMapper
Api, I needed to call Internal()
to access ForAllPropertyMaps()
.
So the solutions to this problem in recent version, based on this answer, is:
using AutoMapper.Internal;
class MapProfile : Profile
{
public MapProfile()
{
// Per Map
CreateMap<MyEntityType, MyComplexType>()
.ForMember(p => p.MyComplexTypeProperty1, p => p.DoNotAllowNull());
// Or for all maps (don't forget the using above)
this.Internal().ForAllPropertyMaps(p =>
p.SourceType == typeof(MyComplexType) ||
p.SourceType == typeof(AnotherComplexType)
,
(p, q) => {
// Null Complex Types are not supported in .Net 8, so don't allow it.
q.DoNotAllowNull();
}
);
}
}
Upvotes: 1
Reputation: 1522
make your prop Nullable
public decimal? MyComplexTypeProperty1 { get;set; }
then use this mapping
Mapper.CreateMap<MyEntityType, MyComplexType>()
.ForMember(p => p.MyComplexTypeProperty1, p => p.AllowNull())
and if you want solve problems for all complex types, then you can use this code in creating MapperConfiguration
var config = new MapperConfiguration(cfg =>
{
cfg.ForAllPropertyMaps(p =>
p.SourceType == typeof(MyComplexType) ||
p.SourceType == typeof(AnotherComplexType) // || ...
//NOTE: if you have another ComplexTypes so remove prev lines
// and use this line to handle all of them
//p.SourceType.GetCustomAttributes(typeof(ComplexType))
(p, q) => { q.AllowNull(); }
);
//other configs
});
Upvotes: 2
Reputation: 1990
LINQ to entities is unable to perform compare (null check) for complex types as you suggested. For example this does not work...
myContext.Select(i => new
{
MyComplexType = i.MyComplexType != null ?
new MyComplexTypeViewModel()
{
Property1 = i.MyComplexType.Property1
}
: null
})
By default Automapper tries to map null source values as nulls and sometimes adds similar conditions in generated expression when using Project().To<>()
or Mapper.Engine.CreateMapExpression<,>()
.
In my case i was mapping whole complex type to it's own viewmodel and did not use property flattening. This configuration value solved the issue for me...
Mapper.AllowNullDestinationValues = false;
You may try to manually create mapping expression using CreateMapExpression<TSource,TDest>()
and look for null checks on complex type to see if it's the same situation.
Upvotes: 6