Closed bhood-zorus closed 10 months ago
I have a similar issue with DateTime and DateTimeOffset:
System.InvalidOperationException: For members of literal types, use IMappingExpression.ForMember() to make the parent property types an exact match. Parent Source Type: System.Nullable1[System.DateTimeOffset], Parent Destination Type: System.Nullable
1[System.DateTime], Full Member Name \"Value.Year\"
The way I was able to workaround this issue even though I have a map defined for DateTimeOffset? <=> DateTime? was to add a .ForMember for each DateTimeOffset? property that mapped to a entity property with DateTime? (all DB has all date columns column set as datetime and we only use UTC) forcing the cast of DateTime? to DateTimeOffset? . It seemed redundant but fixed the issue for me. Probably it may fix your issue also if you create maps for int, int?, uint, ulong, etc.
So I had to add all these maps. I'm not sure if I really need them but it was the only way to make the OData filters work properly when filtering expanded properties for me:
CreateMap<DateTime, DateTimeOffset>().ConvertUsing(src => (DateTimeOffset)src);
CreateMap<DateTime?, DateTimeOffset?>().ConvertUsing(src => (DateTimeOffset?)src.Value);
CreateMap<DateTimeOffset, DateTime>().ConvertUsing(src => src.UtcDateTime);
CreateMap<DateTimeOffset?, DateTime?>().ConvertUsing(src => (DateTime?)src.Value.UtcDateTime);
CreateMap<DateTimeOffset?, DateTimeOffset>().ReverseMap();
CreateMap<DateTimeOffset?, Microsoft.OData.Edm.Date?>().ReverseMap();
On top of that I had to add this cast for each DateTimeOffset otherwise filtering using only date (dateField eq 2024-01-24) would fail with the error above.
.ForMember(dest => dest.DateField, opt => opt.MapFrom(src => (DateTimeOffset?)src.DateField.Value))
You'll want to add the failing expression mapping so anyone trying to help can run it an see it fail e.g.
Expression<Func<TestModel, bool>> expression = src => src.UserId != 1;
Expression<Func<TestEntity, bool>> mappedExpression = mapper.MapExpression<Expression<Func<TestEntity, bool>>>(expression);
Thanks, I've put that into the message above the original reproduction steps.
This doesn't throw:
[Fact]
public void CanMap()
{
var mapper = new AutoMapper.MapperConfiguration(cfg =>
{
cfg.CreateMap<TestEntity, TestModel>();
}).CreateMapper();
Expression<Func<TestModel, bool>> expression = src => src.UserId != 1;
Expression<Func<TestEntity, bool>> mappedExpression = mapper.MapExpression<Expression<Func<TestEntity, bool>>>(expression);
}
// Source: EF entity
public class TestEntity
{
public int Id { get; set; }
public ulong UserId { get; set; }
}
// Destination: API model
public class TestModel
{
public int? Id { get; set; }
public ulong? UserId { get; set; }
}
You can still post the failing expression here or open a new issue.
@BlaiseD You gave me the expression, so I reasonably assumed it would be relevant to the issue. You were also the one who advised that I create the issue in this repository. It seems that I'm running out of options and have to learn the inner workings of this library, at which point I submit the PR with the fix myself.
There is (and always has been) an api project in the steps to reproduce, and it will readily reproduce the problem.
Original issue: https://github.com/AutoMapper/AutoMapper.Extensions.OData/issues/204
Source/destination types
Mapping configuration
Version: 4.0.1
.NET 6 AutoMapper.AspNetCore.OData.EFCore 4.0.1 AutoMapper 12.0.1 AutoMapper.Collection 9.0.0 AutoMapper.Extensions.ExpressionMapping 6.0.4 AutoMapper.Extensions.DependencyInjection 12.0.1
Expected behavior
Results are returned normally
Actual behavior
Steps to reproduce
Expression mapping
Using OData
Sample project: https://github.com/bhood-zorus/AutoMapperBugDemo Send a GET request to
https://localhost:port/api/test?$filter=UserId eq 1
When the
UserId
property ofTestEntity
andTestModel
is of typeint
andint?
(respectively), the API request succeeds. When it's of an unsigned type (uint
,ulong
, etc.) the application throws the exception above.The sample project uses an in-memory database for the sake of providing a simple repro. This behavior is currently being seen with a real-world MySQL database using the Pomelo EF provider.