首页 > 解决方案 > Automapper ProjectTo 和 JSON 列

问题描述

我在 EF Core 中有一些简单的模型(带有使用 Npgsql 的 Postgres 后端)。

public class Test : Model
{
    public string Id { get; set; }
    public TestInfo Info { get; set; }
    public string Name { get; set; }
}

public class TestInfo
{
    public string[] Tags { get; set; }
    public string Name { get; set; }
    public int[] Counts { get; set; } 
}

public class TestDTO
{
    public string[] Tags { get; set; }
    public string Name { get; set; }
    public string InfoName{ get; set; }
    public int[] Counts { get; set; } 
}

EF Core 配置和 Autommaper 配置文件是

 public class TestConfig
    {
        public override void Configure(EntityTypeBuilder<Test> builder)
        {
            builder.HasKey(m => m.Id);
            builder.Property(s => s.Info)
                .HasConversion(
                    v => JsonConvert.SerializeObject(v),
                    v => JsonConvert.DeserializeObject<TestInfo>(v)
                )
                .HasColumnType("jsonb");
            builder.HasIndex(s => s.Info)
                .HasMethod("gin")
                .HasOperators("jsonb_ops");
        }
    }
    
public class TestProfile : Profile
{
    public TestProfile()
    {
         CreateMap<Test, TestDTO>()
                .ForMember(dest => dest.InfoName, opt => opt.MapFrom(src => src.Info.Name))
                .ForMember(dest => dest.Tags, opt => opt.MapFrom(src => src.Info.Tags))
                .ForMember(dest => dest.Counts, opt => opt.MapFrom(src => src.Info.Counts))
                .ReverseMap();
    }
}

我一直在尝试使用 Automapper 的 ProjectTo 进行查询,但无法弄清楚如何使其工作。下面的控制器代码抛出强制错误

[HttpGet]
[Route("/test/{id}")]
public async Task<IActionResult> GetTest([FromRoute (Name = "id")][Required]string id)
{ 
   var t = await _mapper.ProjectTo<TestDTO>(
      _context.Tests.Where(s => s.Id == id)
   )
   .ToListAsync();
   return Ok(t); 
}

错误(从 System.String 到 System.string[] 的标签还有另一个错误,但为简洁起见,我省略了)

System.InvalidOperationException: No coercion operator is defined between types 'System.String' and 'System.Int32[]'.
   at System.Linq.Expressions.Expression.GetUserDefinedCoercionOrThrow(ExpressionType coercionType, Expression expression, Type convertToType)
   at System.Linq.Expressions.Expression.Convert(Expression expression, Type type, MethodInfo method)
   at System.Linq.Expressions.Expression.Convert(Expression expression, Type type)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.CreateGetValueExpression(ParameterExpression dbDataReader, Int32 index, Boolean nullable, RelationalTypeMapping typeMapping, Type type, IPropertyBase property)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at System.Linq.Expressions.Expression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberAssignment(MemberAssignment node)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)
   at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection`1 nodes, Func`2 elementVisitor)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)
   at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ProcessShaper(Expression shaperExpression, RelationalCommandCache& relationalCommandCache, LambdaExpression& relatedDataLoaders)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitShapedQuery(ShapedQueryExpression shapedQueryExpression)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at System.Linq.Expressions.Expression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_0`1.<ExecuteAsync>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at dms_apis.Controllers.OrganisationController.GetTest(String id) in /home/viet/src/dms-apis/Controllers/OrganisationController.cs:line 62
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

我尝试Select在控制器中使用手动映射,这似乎可行,但 ProjectTo 似乎要好得多

[HttpGet]
[Route("/test/{id}")]
public async Task<IActionResult> GetTest([FromRoute (Name = "id")][Required]string id)
{ 
   var t = _context.Tests
           .Select(s => new TestDTO{
               Id = s.Id,
               Name = s.Info.Name
            })
            .Where(s => s.Id == id)
            .ToList();

   return Ok(t); 
}

在这种情况下使用 ProjectTo 的正确方法是什么?非常感谢。

标签: entity-framework-coreautomapper

解决方案


推荐阅读