You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Cannot CompileProjection for a mapping derived types between abstract classes with .Include() #801
When trying to create a mapping between abstract base classes and including their derived concrete implementations, Mapster throws an exception during CompileProjection(). There appears to be an issue in the expression tree generation.
Environment
Mapster version: 7.4.0
Mapster.EFCore 5.1.1
.NET version: net9.0
EF Core version: 9.0.4
I have tried Mapster 7.4.2-pre02 with net9.0 and net8.0 as well.
Alternatively, edit the WhenIncludeDerivedClasses test in Mapster.Tests so that the Compile() lines are CompileProjection();
Expected Behavior
The mapping should compile successfully, allowing for projection between these types.
Actual Behavior
An exception is thrown during CompileProjection():
Unhandled exception. Mapster.CompileException: Error while compiling
source=Backend.Services.Models.PocoA
destination=Backend.Services.Models.DtoA
type=Projection
---> System.InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.UnaryExpression' to type 'System.Linq.Expressions.NewExpression'.
at Mapster.Adapters.ClassAdapter.CreateInlineExpression(Expression source, CompileArgument arg)
at Mapster.Adapters.BaseAdapter.CreateInlineExpressionBody(Expression source, CompileArgument arg)
at Mapster.Adapters.BaseAdapter.CreateExpressionBody(Expression source, Expression destination, CompileArgument arg)
at Mapster.Adapters.BaseAdapter.CreateAdaptFunc(CompileArgument arg)
at Mapster.TypeAdapterConfig.CreateMapExpression(CompileArgument arg)
--- End of inner exception stack trace ---
at Mapster.TypeAdapterConfig.CreateMapExpression(CompileArgument arg)
at Mapster.TypeAdapterConfig.CreateMapExpression(TypeTuple tuple, MapType mapType)
at Mapster.TypeAdapterConfig.CreateProjectionCallExpression(TypeTuple tuple)
at Mapster.TypeAdapterConfig.CompileProjection()
Projection is intended to create a query to the database.
And at the moment of creating a query, we cannot know what derived type will be obtained from the database (if this is even possible).
Even if this can be done, the scope of application will be limited to Linq to Object cases (after loading from the database into memory).
privatestaticreadonlyLambdaExpressionSimpleAddressProjection=TypeAdapterConfig.CreateMapExpression(newTypeTuple(typeof(Address),typeof(AddressDto)),MapType.Projection);privatestaticreadonlyLambdaExpressionDetailedAddressProjection=TypeAdapterConfig.CreateMapExpression(newTypeTuple(typeof(DetailedAddress),typeof(DetailedAddressDto)),MapType.Projection);publicIQueryable<AddressDto>ProjectTo(IQueryable<Address>source){varparam=Expression.Parameter(typeof(Address));varlambda=Expression.Lambda(Expression.Condition(Expression.TypeIs(param,typeof(DetailedAddress)),Expression.TypeAs(Expression.Invoke(DetailedAddressProjection,Expression.TypeAs(param,typeof(DetailedAddress))),typeof(AddressDto)),Expression.Invoke(SimpleAddressProjection,param)),param);varselectExpr=CreateProjectionCallExpression<Address,AddressDto>(lambda);varfinalExpr=Expression.Call(selectExpr.Method,source.Expression,selectExpr.Arguments[1]);returnsource.Provider.CreateQuery<AddressDto>(finalExpr);}// From MapsterprivateMethodCallExpressionCreateProjectionCallExpression<TSource,TDestination>(LambdaExpressionlambda){varsource=Expression.Parameter(typeof(IQueryable<>).MakeGenericType(typeof(TSource)));varmethodInfo=(frommethodintypeof(Queryable).GetMethods()wheremethod.Name==nameof(Queryable.Select)letp=method.GetParameters()[1]wherep.ParameterType.GetGenericArguments()[0].GetGenericTypeDefinition()==typeof(Func<,>)selectmethod).First().MakeGenericMethod(typeof(TSource),typeof(TDestination));returnExpression.Call(methodInfo,source,Expression.Quote(lambda));}
I did notice that a derived source type with no matching Include<TDerivedSource, TDerivedDestination>() registration projects to default(TDestination), i.e. a null row rather than an error. So adding a derived type and forgetting to register it with Include<>() in the Mapster config will silently yield nulls.
Activity
DocSvartz commentedon May 8, 2025
@Tommsy64 .Include to querytable not supported.
Projection is intended to create a query to the database.
And at the moment of creating a query, we cannot know what derived type will be obtained from the database (if this is even possible).
Even if this can be done, the scope of application will be limited to Linq to Object cases (after loading from the database into memory).
zsr2531 commentedon Jul 11, 2025
It is possible to know the type from within the queryable, this works:
I don't know since when, but EFCore supports translating the
.GetType()call.zsr2531 commentedon Jul 11, 2025
I was able to get this working like so:
DocSvartz commentedon Jul 11, 2025
@zsr2531 If
x.GetType() == typeof(DetailedAddress)it really is converted into a SQL query to the database. In this case adding is really possible.Can you show the resulting SQL query?
zsr2531 commentedon Jul 11, 2025
Sure:
It seems like they lower
x.GetType() == ...to a discriminator column check.zsr2531 commentedon Jul 11, 2025
Forgot to mention, but I am also on EFCore version 9.
DocSvartz commentedon Jul 11, 2025
Yes, if it can work, then only like this :)
DocSvartz commentedon Jul 11, 2025
@zsr2531 It's a kind of filter.
But .Include works differently.
Using the source type It create instance of destination type defined in the config.
DocSvartz commentedon Jul 11, 2025
It seems like an idea of how this can be done has appeared :)
But it will be done, not quickly.
fix: support CompileProjection with Include on abstract types (#801)
chore: Merge pull request #977 from leno23/fix/projection-include-abs…
Tommsy64 commentedon Aug 20, 2026
Fixed as of Mapster 10.0.9. Closing this issue.
I did notice that a derived source type with no matching
Include<TDerivedSource, TDerivedDestination>()registration projects todefault(TDestination), i.e. a null row rather than an error. So adding a derived type and forgetting to register it withInclude<>()in the Mapster config will silently yield nulls.