Skip to content

Mapster.EFCore ProjectToType<T>().FirstOrDefaultAsync throwing exception #820

Closed

Description

@ScarletKuro

Very similar problem as here #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

Activity

  1. ScarletKuro commented on Oct 13, 2025

    @ScarletKuro
    ContributorAuthor

    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.

  2. DocSvartz commented on Oct 13, 2025

    @DocSvartz
    Contributor

    @ScarletKuro Yes, The problem here is that FirstOrDefaultAsync() doesn't return an Enumerator, it return Task
    But for some reason, it's handled by the Enumerator code.

  3. ScarletKuro commented on Oct 14, 2025

    @ScarletKuro
    ContributorAuthor

    Made a PR #821 with a test. Hopefully this can be merged soon and released so I don't need to drag a custom version.

  4. DocSvartz commented on Oct 14, 2025

    @DocSvartz
    Contributor

    @ScarletKuro Great job! 👍

  5. ScarletKuro commented on Mar 6, 2026

    @ScarletKuro
    ContributorAuthor

    Guess this can be closed if #823 was merged in the main?

  6. DocSvartz commented on Mar 6, 2026

    @DocSvartz
    Contributor

    @ScarletKuro Yes, it is will be merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions