带有 GraphQL 的 EF Core
Posted
技术标签:
【中文标题】带有 GraphQL 的 EF Core【英文标题】:EF Core with GraphQL 【发布时间】:2019-06-21 11:53:50 【问题描述】:我目前正在探索 GraphQL 开发,我目前正在探索通过 EF Core 生成什么样的 SQL 查询,我观察到,无论我的 GraphQL 查询只包含几个字段,EF Core 都会为所有人发送 SQL Select实体的字段。
这是我现在使用的代码:
public class DoctorType : ObjectGraphType<Doctors>
public DoctorType()
Field(d => d.PrefixTitle);
Field(d => d.FName);
Field(d => d.MName);
Field(d => d.LName);
Field(d => d.SufixTitle);
Field(d => d.Image);
Field(d => d.EGN);
Field(d => d.Description);
Field(d => d.UID_Code);
public class Doctors : ApplicationUser
public string Image get; set;
[StringLength(50)]
public string UID_Code get; set;
我使用的查询是
doctors
fName
lName
生成的 SQL 选择 Doctor 实体的所有字段。
有没有办法进一步优化从 EF Core 生成的 SQL 查询?
我猜这是因为 DoctorType 继承自 ObjectGraphType<Doctors>
而不是来自 Doctor 的某个 Projection,但我想不出一个聪明的解决方法?
有什么建议吗?
编辑:
我正在使用 Joe McBride 2.4.0 版的 GraphQL.NET (graphql-dotnet)
编辑 2:
要么我做错了,要么我不知道。
正如其中一位 cmets 建议的那样,我下载了 SimonCropp 的 GraphQL.EntityFramework Nuget 包
我做了所有需要的配置:
services.AddDbContext<ScheduleDbContext>(options =>
options.Usemysql(Configuration.GetConnectionString("DefaultConnection"));
);
using (var myDataContext = new ScheduleDbContext())
EfGraphQLConventions.RegisterInContainer(services, myDataContext);
我的对象图类型如下所示
public class SpecializationType : EfObjectGraphType<Specializations>
public SpecializationType(IEfGraphQLService graphQlService)
:base(graphQlService)
Field(p => p.SpecializationId);
Field(p => p.Code);
Field(p => p.SpecializationName);
我的查询看起来是:
public class RootQuery : EfObjectGraphType
public RootQuery(IEfGraphQLService efGraphQlService,
ScheduleDbContext dbContext) : base(efGraphQlService)
Name = "Query";
AddQueryField<SpecializationType, Specializations>("specializationsQueryable", resolve: ctx => dbContext.Specializations);
我正在使用这个 graphQL 查询
specializationsQueryable
specializationName
调试日志显示生成的SQL查询是
SELECT `s`.`SpecializationId`, `s`.`Code`, `s`.`SpecializationName`
FROM `Specializations` AS `s`
即使我只想要 specializationName 字段并且我希望它是:
SELECT `s`.`SpecializationName`
FROM `Specializations` AS `s`
更新
我想到目前为止我还不明白 graphQL 是如何工作的。我认为有一些幕后数据获取,但没有。
主要提取在查询的字段解析器中完成:
FieldAsync<ListGraphType<DoctorType>>("doctors", resolve: async ctx => await doctorServices.ListAsync());
只要解析器的结果是完整对象,在我的例子中,解析器返回Doctors
实体列表,它将查询整个实体(所有字段)的数据库。如果您返回 IQueryable 或您正在查询的实体,GraphQL 没有开箱即用的优化。
这里的每一个结论都是我的想法,并不是100%保证正确
所以我所做的是创建一组帮助器方法,这些方法正在创建一个选择表达式以在 LINQ 查询中使用。助手使用解析器的 context.SubFields 属性来获取所需的字段。
问题是您只需要查询每个级别的叶子,例如一些带有“SpecializationName”和“代码”的查询“专业化”以及带有“名称”的“医生”等等。在这种情况下,在 RootQuery
专业化字段的解析器中,您只需要 Specializations
实体投影,因此:SpecializationName
和 Code
,然后当它从 SpecializationType
的“医生”字段中获取所有 Doctors
时解析器的上下文有不同的子字段,应该用于Doctor
的投影。
上面的问题是,当您使用查询批处理时,我猜即使您不知道SpecializationType
中的Doctors
字段需要在RootQuery
专业化字段中获取的专业化ID。
我想我没有很好地解释我经历了什么。
据我了解,基线是我们必须动态创建 linq 应该用来投影实体的选择器。
我在这里发布我的方法:
public class RootQuery : EfObjectGraphType
public RootQuery(IEfGraphQLService efGraphQlService, ISpecializationGraphQlServices specializationServices,
IDoctorGraphQlServices doctorServices, ScheduleDbContext dbContext) : base(efGraphQlService)
Name = "Query";
FieldAsync<ListGraphType<SpecializationType>>("specializations"
, resolve: async ctx =>
var selectedFields = GraphQLResolverContextHelpers.GetFirstLevelLeavesNamesPascalCase(ctx.SubFields);
var expression = BuildLinqSelectorObject.DynamicSelectGenerator<Specializations>(selectedFields.ToArray());
return await specializationServices.ListAsync(selector: expression);
);
专业化类型
public class SpecializationType : EfObjectGraphType<Specializations>
public SpecializationType(IEfGraphQLService graphQlService
, IDataLoaderContextAccessor accessor, IDoctorGraphQlServices doctorServices)
: base(graphQlService)
Field(p => p.SpecializationId);
Field(p => p.Code);
Field(p => p.SpecializationName);
Field<ListGraphType<DoctorType>, IEnumerable<Doctors>>()
.Name("doctors")
.ResolveAsync(ctx =>
var selectedFields = GraphQLResolverContextHelpers.GetFirstLevelLeavesNamesPascalCase(ctx.SubFields);
selectedFields = GraphQLResolverContextHelpers.AppendParrentNodeToEachItem(selectedFields, parentNode: "Doctor");
selectedFields = selectedFields.Union(new[] "Specializations_SpecializationId" );
var expression = BuildLinqSelectorObject.BuildSelector<SpecializationsDoctors, SpecializationsDoctors>(selectedFields);
var doctorsLoader = accessor.Context
.GetOrAddCollectionBatchLoader<int, Doctors>(
"GetDoctorsBySpecializationId"
, (collection, token) =>
return doctorServices.GetDoctorsBySpecializationIdAsync(collection, token, expression);
);
return doctorsLoader.LoadAsync(ctx.Source.SpecializationId);
);
医生服务:
public class DoctorGraphQlServices : IDoctorGraphQlServices
public ScheduleDbContext _dbContext get; set;
public DoctorGraphQlServices(ScheduleDbContext dbContext)
_dbContext = dbContext;
public async Task<List<Doctors>> ListAsync(int? specializationId = null)
var doctors = _dbContext.Doctors.AsQueryable();
if(specializationId != null)
doctors = doctors.Where(d => d.Specializations.Any(s => s.Specializations_SpecializationId == specializationId));
return await doctors.ToListAsync();
public async Task<ILookup<int, Doctors>> GetDoctorsBySpecializationIdAsync(IEnumerable<int> specializationIds, CancellationToken token, Expression<Func<SpecializationsDoctors, SpecializationsDoctors>> selector = null)
var doctors = await _dbContext.SpecializationsDoctors
.Include(s => s.Doctor)
.Where(spDocs => specializationIds.Any(sp => sp == spDocs.Specializations_SpecializationId))
.Select(selector: selector)
.ToListAsync();
return doctors.ToLookup(i => i.Specializations_SpecializationId, i => i.Doctor);
专业化服务
public class SpeciaizationGraphQlServices : ISpecializationGraphQlServices
public ScheduleDbContext _dbContext get; set;
public SpeciaizationGraphQlServices(ScheduleDbContext dbContext)
_dbContext = dbContext;
public async Task<dynamic> ListAsync(string doctorId = null, Expression<Func<Specializations, Specializations>> selector = null)
var specializations = _dbContext.Specializations.AsQueryable();
if (!string.IsNullOrEmpty(doctorId))
specializations = specializations.Where(s => s.Doctors.Any(d => d.Doctors_Id == doctorId));
return await specializations.Select(selector).ToListAsync();
public async Task<ILookup<string, Specializations>> GetSpecializationsByDoctorIdAsync(IEnumerable<string> doctorIds, CancellationToken token)
var specializations = await _dbContext.SpecializationsDoctors
.Include(s => s.Specialization)
.Where(spDocs => doctorIds.Any(sp => sp == spDocs.Doctors_Id))
.ToListAsync();
return specializations.ToLookup(i => i.Doctors_Id, i => i.Specialization);
public IQueryable<Specializations> List(string doctorId = null)
var specializations = _dbContext.Specializations.AsQueryable();
if (!string.IsNullOrEmpty(doctorId))
specializations = specializations.Where(s => s.Doctors.Any(d => d.Doctors_Id == doctorId));
return specializations;
这篇文章已经变得相当大,抱歉跨度..
【问题讨论】:
EF Core 配置和映射是什么样的?你如何加载数据? 您使用的是哪个 GraphQL 库? GraphQL.NET 是常见的,但不是唯一的。没有 GraphQL.EntityFramework 就没有到 EF 的直接映射 您的意思是 GraphQL.EntityFramework 处理与 GraphQL 查询字段请求匹配的 SQL SELECT 查询的生成? 我会阅读 GraphQL.EntityFramework 文档。感谢您展示前进的道路。 【参考方案1】:对于DoctorType
,检查定义的ObjectGraphType
,它用于返回Doctors
。
例如,我有PlayerType
,如下所示:
public class PlayerType : ObjectGraphType<Player>
public PlayerType(ISkaterStatisticRepository skaterStatisticRepository)
Field(x => x.Id);
Field(x => x.Name, true);
Field(x => x.BirthPlace);
Field(x => x.Height);
Field(x => x.WeightLbs);
Field<StringGraphType>("birthDate", resolve: context => context.Source.BirthDate.ToShortDateString());
Field<ListGraphType<SkaterStatisticType>>("skaterSeasonStats",
arguments: new QueryArguments(new QueryArgument<IntGraphType> Name = "id" ),
resolve: context => skaterStatisticRepository.Get(context.Source.Id), description: "Player's skater stats");
然后我通过
返回Field<ListGraphType<PlayerType>>
public class NHLStatsQuery : ObjectGraphType
public NHLStatsQuery(IPlayerRepository playerRepository, NHLStatsContext dbContext)
Field<ListGraphType<PlayerType>>(
"players",
resolve: context =>
return dbContext.Players.Select(p =>new Player Id = p.Id, Name = p.Name );
//return playerRepository.All();
);
对于查询及其列,它由字段中的resolve
控制。
无论您要返回什么字段,请确保PlayerType
中定义的列在resolve
中返回。
【讨论】:
此代码显式请求 EF 查询中的Id, Name
列。它不使用 GraphQL 查询来决定返回哪些字段
@PanagiotisKanavos 你是对的。我用这种方式来控制NHLStatsQuery
生成的查询。有没有更好的方法来控制 GraphQL 生成的查询?【参考方案2】:
@jeremylikness 在 .NET Conf 2021 上讨论了使用 EF Core 6 的 GraphQL。我建议使用 .NET 6 并查看他的演讲:
https://devblogs.microsoft.com/dotnet/get-to-know-ef-core-6/#graphql
https://aka.ms/graphql-efcore
https://www.youtube.com/watch?v=GBvTRcV4PVA
https://www.youtube.com/watch?v=4nqjB_z5CU0
这是一个使用 Hot Chocolate GraphQL 服务器的示例实现:
https://chillicream.com/docs/hotchocolate/integrations/entity-framework
这是 Microsoft 在其高级计划中关于 GraphQL for EF Core 6.0 的内容:
GraphQL 在过去几年中越来越受欢迎 各种平台。我们计划调查这个空间并找到方法 改善使用 .NET 的体验。这将涉及与 社区了解和支持现有的生态系统。 它还可能涉及微软的特定投资,无论是在 对现有工作的贡献形式或在开发中补充 Microsoft 堆栈中的部分。
https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-6.0/plan#graphql
【讨论】:
【参考方案3】:我建议你:
1-使用 dto 模型并将它们与数据库模型映射
这表示需要将输入的d转为database model中的model才能保存到db中;并将从实体框架数据库选择中获得的数据库模型转换为dto模型。
这是您制作通用 api 时使用的经典方法,例如在输入请求中获取 dto 模型数据,将 dto 转换为将数据保存到数据库中,反之亦然。
2-map dto 模型到 graphqltypes(objectgraphtype 和 inputobjectgraphtype)
这意味着可能需要为每个 dto 模型编写 1 个 objectgraphtype 和 1 个 inputobjectgraphtype。
为此,我创建了一个自动 DTO 到图形转换器,因此您无需编写 K 和 K 的代码! (见文末链接)
3-不要使用 ADDDBCONTEXT! Graphql 中间件使用单例模式; graphql 中通过 Dependecy 注入使用的所有内容在外部都是单例的,即使它被注册为作用域(AddDbContext 表示“作用域”)。
这意味着您打开了 1 个连接以启动。您不能同时进行 2 个数据库操作!
在现实生活中你不能将 AddDbContext 与 Graphql 一起使用!
您可以使用工厂模式来执行此操作。因此,不要在依赖注入中传递 dbcontext,而是通过 Func 并显式实例化 dbcontext。
这里有一个完整的实现示例: https://github.com/graphql-dotnet/graphql-dotnet/issues/576#issuecomment-626661695
【讨论】:
【参考方案4】:要启用自动字段投影,像往常一样为 .NET 6 设置数据库上下文,添加 hotchocolate 服务器:
dotnet add package HotChocolate.Data.EntityFramework
向它公开一些数据:
public class MyQueries
[UseProjection] // Enables field projection
public IQueryable<Book> Books([Service] MyContext db) => db.Books;
在Program.cs
启用它:
builder.Services.AddGraphQLServer().AddQueryType<MyQueries>().AddProjections();
...
app.MapGraphQL("/graphql");
这应该足以确保自动 db 字段投影。现在您可以通过/graphql/
生成的builder 运行GraphQL 查询,同时通过MyContext.Database.Log = Console.Write;
监控SQL
【讨论】:
【参考方案5】:我正在使用 Joe McBride 2.4.0 版的 GraphQL.NET (graphql-dotnet)
首先,我建议至少更新到 v4.6 - 有很多修复和有用的更新。
其次,如果您没有数据突变(意味着 - 更新/删除/插入数据),我会说最好不要使用 EF 来获取数据。基于同一个 GraphQL.Net lib 你可以看一下,例如NReco.GraphQL 正在使用轻量级 ORM 来获取和映射数据(你只需要在 json-file 中定义一个模式)。
【讨论】:
以上是关于带有 GraphQL 的 EF Core的主要内容,如果未能解决你的问题,请参考以下文章