我有一个解析器,它从 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()
将其添加到我自己的选择中时,它也会出现问题,但我不知道该集合与此有什么关系,因为它仅在从会话集合中请求字段时才会失败
如果您确定不需要空检查,请使用
DoNotAllowNull
。
这告诉 AM 跳过空检查。