带有GraphQL的EF Core

时间:2019-01-28 13:05:18

标签: c# asp.net-core entity-framework-core graphql graphql.entityframework

我目前正在探索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的投影,但是我想不出一个聪明的解决方法?

有什么建议吗?

编辑:

我正在使用Joe McBride 2.4.0版的GraphQL.NET(graphql-dotnet)

编辑2:

我做错了还是不知道。

作为评论之一,我下载了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%保证的权利

所以我所做的是创建一组Helper方法,这些方法正在创建要在LINQ查询中使用的选择表达式。帮助程序正在使用解析器的context.SubFields属性来获取所需的字段。

问题是,对于查询的每个级别,您只需要叶子,例如使用“ SpecializationName”和“ Code”说一些查询“ specializations”,用“ Name”说“ Doctors”等等。在这种情况下,在RootQuery专业化字段的解析器中,您仅需要Specializations实体投影,即:SpecializationNameCode,则当它获取所有Doctors时在SpecializationType的“医生”字段中,解析器的上下文具有不同的子字段,应将其用于Doctor的投影。

上述问题是,即使您不使用查询批处理,我也猜想Doctors中的SpecializationType字段需要在RootQuery专业化中获取的SpecializationId字段。

我想我没有很好地解释我经历过的事情。

据我所知,我们必须动态创建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);
            });
    }
}

SpecializationType

 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);
            });
    }
}

DoctorsServices:

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);
    }

}

SpecializationServices

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;
    }
}

这篇文章已经变得很大,很抱歉。

2 个答案:

答案 0 :(得分:0)

对于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中返回在{{1}}中定义的列。

答案 1 :(得分:0)

我建议你:

1-使用dto模型并将其与数据库模型映射

这意味着您需要将输入dto模型转换为数据库模型以保存到db中;并将从实体框架数据库选择中获得的数据库模型转换为dto模型。

这是制作通用api时使用的经典方法,例如,在输入请求中获取dto模型数据,将dto转换为将数据保存在数据库中,反之亦然。

2-dto模型映射到graphqltypes(objectgraphtype和inputobjectgraphtype)

这意味着可能需要为每个dto模型编写1个objectgraphtype和1个inputobjectgraphtype。

为此,我创建了一个自动DTO到GRAPHTYPE CONVERTER,因此您无需编写K和K的代码! (请参阅最后的链接)

3-请勿使用ADDDBCONTEXT! Graphql中间件使用单例模式;在Graphql中通过Dependecy注入使用的所有内容都是外部单例的,即使它已注册为作用域(AddDbContext表示“作用域”)。

这意味着您已打开1个连接以进行启动。您不能同时执行2 db操作!

在现实生活中,您无法将Graphql与AddDbContext一起使用!

您可以使用工厂模式来执行此操作。因此,不要在Dependency Injection中传递dbcontext,而要通过Func显式实例化dbcontext。

这里有一个完整的实现示例: https://github.com/graphql-dotnet/graphql-dotnet/issues/576#issuecomment-626661695