Mapster.EFCore ProjectToType<T>().FirstOrDefaultAsync throwing exception
Very similar problem as here https://github.com/MapsterMapper/Mapster/issues/566 But I don't think that "materializing the query" is an acceptable workaround:
This WORKS (without using IMapper)
var template = await query
.ProjectToType<TemplateDto>()
.FirstOrDefaultAsync();
This WORKS (with IMapper but FirstOrDefault)
var template = _mapper.From(query)
.ProjectToType<TemplateDto>()
.FirstOrDefault();
This doesn't WORK (With IMapper but FirstOrDefaultAsync)
var template = await _mapper.From(query)
.ProjectToType<TemplateDto>()
.FirstOrDefaultAsync();
And thows:
System.MissingMethodException
HResult=0x80131513
Message=Constructor on type 'Mapster.EFCore.MapsterAsyncEnumerable`1[[MessageDispatcher.Shared.Restful.Api.Dto.TemplateDto, MessageDispatcher.Shared, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' not found.
Source=System.Private.CoreLib
StackTrace:
at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)
at System.Activator.CreateInstance(Type type, Object[] args)
at Mapster.EFCore.MapsterQueryableProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstOrDefaultAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
at MessageDispatcher.Services.TemplateService.<GetTemplateByNameAsync>d__7.MoveNext() in C:\Users\Dell\source\repos\MessageDispatcher\MessageDispatcher\Services\TemplateService.cs:line 64
at MessageDispatcher.Controllers.TemplateController.<GetTemplateByName>d__3.MoveNext() in C:\Users\Dell\source\repos\MessageDispatcher\MessageDispatcher\Controllers\TemplateController.cs:line 38
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.<Execute>d__0.MoveNext()
at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<<InvokeActionMethodAsync>g__Logged|12_1>d.MoveNext()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<<InvokeNextActionFilterAsync>g__Awaited|10_0>d.MoveNext()
The mapper itself
public class MapsterConfig : IRegister
{
public void Register(TypeAdapterConfig config)
{
...
config.NewConfig<TemplateTranslationEntity, TemplateDto.TranslationDto>()
.ConstructUsing(locale => new TemplateDto.TranslationDto(locale.Locale, locale.Text));
config.NewConfig<TemplateEntity, TemplateDto>()
.ConstructUsing(template => new TemplateDto(template.Name, template.Translations.Adapt<IReadOnlyList<TemplateDto.TranslationDto>>()));
...
}
}
public class TemplateTranslationEntity
{
public long Id { get; set; }
public long TemplateId { get; set; }
public string Locale { get; set; } = null!;
public string Text { get; set; } = null!;
public TemplateEntity Template { get; set; } = null!;
public LocaleEntity LocaleInfo { get; set; } = null!;
}
public class TemplateDto
{
[JsonPropertyName("name")]
public string Name { get; }
[JsonPropertyName("translations")]
public IReadOnlyList<TranslationDto> Translations { get; }
[JsonConstructor]
public TemplateDto(string name, IReadOnlyList<TranslationDto> translations)
{
Name = name;
Translations = translations;
}
public class TranslationDto
{
[JsonPropertyName("languageCode")]
public string LanguageCode { get; }
[JsonPropertyName("content")]
public string Content { get; }
[JsonConstructor]
public TranslationDto(string languageCode, string content)
{
LanguageCode = languageCode;
Content = content;
}
}
}
Why does await query.ProjectToType is different from _mapper.From(query).ProjectToType?
NB! Switching from constructor to property mapping gave the same error.
Additional info:
.NET 9.0 Mapster 7.4.0 Mapster.EFCore 5.1.1 Mapster.DependencyInjection 1.0.1
I don't think it should even wrap in MapsterAsyncEnumerable for FirstOrDefaultAsync here?
Shouldn't it be something like
public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken = default)
{
var enumerable = ((IAsyncQueryProvider)_provider).ExecuteAsync<TResult>(expression, cancellationToken);
var enumerableType = typeof(TResult);
+ if (!IsAsyncEnumerableType(enumerableType))
+ {
+ return enumerable;
+ }
var elementType = enumerableType.GetGenericArguments()[0];
...
}
+private static bool IsAsyncEnumerableType(Type type)
+{
+ return type.GetInterfaces()
+ .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>));
+}
in MapsterQueryable.cs
At least it fixes the problem, but I didn't test it properly yet.
@ScarletKuro Yes, The problem here is that FirstOrDefaultAsync() doesn't return an Enumerator, it return Task<T> But for some reason, it's handled by the Enumerator code.
Made a PR https://github.com/MapsterMapper/Mapster/pull/821 with a test. Hopefully this can be merged soon and released so I don't need to drag a custom version.
@ScarletKuro Great job! 👍