当模型包含集合时,热巧克力和自动映射器无法翻译

问题描述 投票:0回答:1

我有一个解析器,它从 Entity Framework Core 返回 IQueryable。当我直接返回 EF Core 实体时,一切正常。但是,当我尝试将 EF Core 实体映射到我的 GraphQL 模型时,查询无法正确转换,特别是当模型包含集合时

当我使用 ProjectTo 提出此请求时它失败了,我得到了响应:

{
  "errors": [
    {
      "message": "Unexpected Execution Error",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "patients"
      ],
      "extensions": {
        "message": "The LINQ expression 'p2 => new Session{ Token = p2.Token }\r\n' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.",
        "stackTrace": "   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitLambda[T](Expression`1 lambdaExpression)\r\n   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.TranslateInternal(Expression expression, Boolean applyDefaultTypeMapping)\r\n   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.TranslateProjection(Expression expression, Boolean applyDefaultTypeMapping)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberAssignment(MemberAssignment memberAssignment)\r\n   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberInit(MemberInitExpression memberInitExpression)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitConditional(ConditionalExpression conditionalExpression)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberAssignment(MemberAssignment memberAssignment)\r\n   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberInit(MemberInitExpression memberInitExpression)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Translate(SelectExpression selectExpression, Expression expression)\r\n   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateSelect(ShapedQueryExpression source, LambdaExpression selector)\r\n   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)\r\n   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)\r\n   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)\r\n   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)\r\n   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)\r\n   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_0`1.<ExecuteAsync>b__0()\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)\r\n   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()\r\n   at HotChocolate.DefaultAsyncEnumerableExecutable`1.ToListAsync(CancellationToken cancellationToken)\r\n   at HotChocolate.Execution.ListPostProcessor`1.ToCompletionResultAsync(Object result, CancellationToken cancellationToken)\r\n   at HotChocolate.Execution.Processing.Tasks.ResolverTask.ExecuteResolverPipelineAsync(CancellationToken cancellationToken)\r\n   at HotChocolate.Execution.Processing.Tasks.ResolverTask.TryExecuteAsync(CancellationToken cancellationToken)"
      }
    }
  ],
  "data": null
}

当我直接使用 IQueryable 运行它时,我确实得到了预期的结果:

{
  "data": {
    "patients": [
      {
        "user": {
          "address": {
            "street": "Elm St"
          },
          "sessions": [
            {
              "token": "token6"
            }
          ]
        }
      },
      {
        "user": {
          "address": {
            "street": "Pine Rd"
          },
          "sessions": [
            {
              "token": "token9"
            }
          ]
        }
      }
    ]
  }
}

如果我从 Graphql 模型中删除列表(并且也不请求它),它也会按预期工作

请求查询:

query {
  patients {
    user {
      address {
        street
      }
      sessions{
        token
      }
    }
  }
}

PatientRepository 方法

public IQueryable<Data.Table.Patient> GetPatientsQueryable()
{
    return Context.Patients.AsNoTracking().AsQueryable();
}

解析器示例

[UseProjection]        
public async Task<IQueryable<Patient>> GetPatients([Service] IPatientRepository repository, [Service] IMapper Mapper)
{
    //DOES NOT WORK
    var dataModelQueryable = repository.GetPatientsQueryable();
    var graphqlModelQueryable = Mapper.ProjectTo<Patient>(dataModelQueryable);

    return graphqlModelQueryable;
}

[UseProjection]
public async Task<IQueryable<Data.Tables.Patient>> GetPatients([Service] IPatientRepository repository, [Service] IMapper Mapper)
{
    //WORKS
    var dataModelQueryable = repository.GetPatientsQueryable();
    return dataModelQueryable;
}

graphql 模型

namespace Api.GraphQL.Models
{
    public class Patient
    {
        public User User { get; set; }

        public BloodType BloodType { get; set; }

        public int Height { get; set; }

        public decimal Weight { get; set; }
    }
}

namespace Api.GraphQL.Models
{
    public class User
    {
        public int Bsn { get; set; }

        public string FirstName { get; set; }
        public string MiddleName { get; set; } = string.Empty;
        public string LastName { get; set; }

        public DateOnly DateOfBirth { get; set; }

        //WHEN REMOVING THIS LIST BOTH METHODS WORK 
        public List<Session> Sessions { get; set; }
        public Address Address { get; set; }
    }
}

namespace Api.GraphQL.Models
{
    public class Session
    {
        public string Token { get; set; }
        public DateTime ExpirationDate { get; set; }
    }
}

EF Core 表

namespace Data.Tables
{
    public class Patient
    {
        [Key, ForeignKey(nameof(User)), DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int UserBsn { get; set; }
        public virtual User User { get; set; }

        public BloodType BloodType { get; set; }

        public required int Height { get; set; }

        [Column(TypeName = "decimal(5,2)")]
        public required decimal Weight { get; set; }
    }
}

namespace Data.Tables
{
    public class User
    {
        [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int Bsn { get; set; }

        [MaxLength(255)]
        public required string? FirstName { get; set; }
        
        [MaxLength(255)]
        public string MiddleName { get; set; } = string.Empty;
        
        [MaxLength(255)]
        public required string LastName { get; set; }

        public DateOnly DateOfBirth { get; set; }

        public ICollection<Session> Sessions { get; set; } 
    }
}

namespace Data.Tables
{
    public class Session
    {
        [Key]
        public int Id { get; set; }

        [ForeignKey(nameof(User))]
        public int UserBsn { get; set; }
        public virtual User User { get; set; }

        [MaxLength(128)]
        public required string Token { get; set; }

        public required DateTime ExpirationDate { get; set; }
    }
}
using AutoMapper;

namespace Api
{
    public class MappingProfile : Profile
    {
        /// <summary>
        /// Mapping profile for AutoMapper (data tables to graphql models)
        /// </summary>
        public MappingProfile()
        {
            CreateMap<Data.Tables.Patient, GraphQL.Models.Patient>();
            CreateMap<GraphQL.Models.Patient, Data.Tables.Patient>();

            CreateMap<Data.Tables.User, GraphQL.Models.User>();
            CreateMap<GraphQL.Models.User, Data.Tables.User>();

            CreateMap<Data.Tables.Session, GraphQL.Models.Session>();
            CreateMap<GraphQL.Models.Session, Data.Tables.Session>();

            CreateMap<Data.Tables.Address, GraphQL.Models.Address>();
            CreateMap<GraphQL.Models.Address, Data.Tables.Address>();
        }
    }
}

额外:

使用 EntityFramework 记录编译时,会话映射方式看起来是相同的

但是自动映射器给出了错误,而我自己的 .Select 没有给出错误

自己选择:

public async Task<IQueryable<Patient>> GetPatients2([Service] IPatientRepository repository, [Service] IMapper Mapper)
{

    var dataModelQueryable = repository.GetPatientsQueryable();
    var graphqlModelQueryable = dataModelQueryable.Select(dtoPatient => new GraphQL.Models.Patient()
    {
        BloodType = dtoPatient.BloodType,
        Height = dtoPatient.Height,
        User = new GraphQL.Models.User()
        {
            Bsn = dtoPatient.User.Bsn,
            Address = new GraphQL.Models.Address()
            {
                City = dtoPatient.User.Address.City,
                CountryOfResidence = dtoPatient.User.Address.CountryOfResidence,
                HouseNumber = dtoPatient.User.Address.HouseNumber,
                Id = dtoPatient.User.Address.Id,
                PostalCode = dtoPatient.User.Address.PostalCode,
                Province = dtoPatient.User.Address.PostalCode,
                Street = dtoPatient.User.Address.Street,
            },
            DateOfBirth = dtoPatient.User.DateOfBirth,
            FirstName = dtoPatient.User.FirstName,
            MiddleName = dtoPatient.User.MiddleName,
            LastName = dtoPatient.User.LastName,
            Sessions = dtoPatient.User.Sessions.Select(dtoSession => new GraphQL.Models.Session()
            {
                ExpirationDate = dtoSession.ExpirationDate,
                Token = dtoSession.Token,
            }).ToList()
        },
        Weight = dtoPatient.Weight
    });

    return graphqlModelQueryable;
}
dbug: 18/10/2024 20:20:43.318 CoreEventId.QueryCompilationStarting[10111] (Microsoft.EntityFrameworkCore.Query)
      Compiling query expression:
      'DbSet<Patient>()
          .AsNoTracking()
          .Select(dtoPatient => new Patient{
              BloodType = dtoPatient.BloodType,
              Height = dtoPatient.Height,
              User = new User{
                  Bsn = dtoPatient.User.Bsn,
                  Address = new Address{
                      City = dtoPatient.User.Address.City,
                      CountryOfResidence = dtoPatient.User.Address.CountryOfResidence,
                      HouseNumber = dtoPatient.User.Address.HouseNumber,
                      Id = dtoPatient.User.Address.Id,
                      PostalCode = dtoPatient.User.Address.PostalCode,
                      Province = dtoPatient.User.Address.PostalCode,
                      Street = dtoPatient.User.Address.Street
                  }
                  ,
                  DateOfBirth = dtoPatient.User.DateOfBirth,
                  FirstName = dtoPatient.User.FirstName,
                  MiddleName = dtoPatient.User.MiddleName,
                  LastName = dtoPatient.User.LastName,
                  Sessions = dtoPatient.User.Sessions
                      .Select(dtoSession => new Session{
                          ExpirationDate = dtoSession.ExpirationDate,
                          Token = dtoSession.Token
                      }
                      )
                      .ToList()
              }
              ,
              Weight = dtoPatient.Weight
          }
          )
          .Select(_s1 => new Patient{ User = _s1.User != null ? new User{
                  Address = _s1.User.Address != null ? new Address{ Street = _s1.User.Address.Street }
                   : null,
                  Sessions = _s1.User.Sessions
                      .Select(p2 => new Session{ Token = p2.Token }
                      )
                      .ToList()
              }
               : null }
          )'

自动映射器:

      'DbSet<Patient>()
          .AsNoTracking()
          .Select(dtoPatient => new Patient{
              User = dtoPatient.User == null ? null : new User{
                  Bsn = dtoPatient.User.Bsn,
                  FirstName = dtoPatient.User.FirstName,
                  MiddleName = dtoPatient.User.MiddleName,
                  LastName = dtoPatient.User.LastName,
                  DateOfBirth = dtoPatient.User.DateOfBirth,
                  Sessions = dtoPatient.User.Sessions
                      .Select(dtoSession => new Session{
                          Token = dtoSession.Token,
                          ExpirationDate = dtoSession.ExpirationDate
                      }
                      )
                      .ToList(),
                  Address = dtoPatient.User.Address == null ? null : new Address{
                      Id = dtoPatient.User.Address.Id,
                      Street = dtoPatient.User.Address.Street,
                      HouseNumber = dtoPatient.User.Address.HouseNumber,
                      PostalCode = dtoPatient.User.Address.PostalCode,
                      City = dtoPatient.User.Address.City,
                      Province = dtoPatient.User.Address.Province,
                      CountryOfResidence = dtoPatient.User.Address.CountryOfResidence
                  }

              }
              ,
              BloodType = dtoPatient.BloodType,
              Height = dtoPatient.Height,
              Weight = dtoPatient.Weight
          }
          )
          .Select(_s1 => new Patient{ User = _s1.User != null ? new User{
                  Address = _s1.User.Address != null ? new Address{ Street = _s1.User.Address.Street }
                   : null,
                  Sessions = _s1.User.Sessions
                      .Select(p2 => new Session{ Token = p2.Token }
                      )
                      .ToList()
              }
               : null }
          )'

将问题定位到空检查

dtoPatient.User == null ? null :  new GraphQL.Models.User()

将其添加到我自己的选择中时,它也会出现问题,但我不知道该集合与此有什么关系,因为它仅在从会话集合中请求字段时才会失败

entity-framework-core automapper hotchocolate
1个回答
0
投票

如果您确定不需要空检查,请使用

DoNotAllowNull
。 这告诉 AM 跳过空检查。

© www.soinside.com 2019 - 2024. All rights reserved.