EF Core с GraphQL - PullRequest
       15

EF Core с GraphQL

0 голосов
/ 28 января 2019

В настоящее время я изучаю разработку GraphQL и сейчас изучаю, какие типы SQL-запросов генерируются через EF Core, и я заметил, что независимо от того, что мой запрос 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.

Есть ли способ дальнейшей оптимизации того, что сгенерированный SQL-запрос из EF Core?

Я предполагаю, что это происходит потому, что DoctorType наследуется от ObjectGraphType<Doctors> ине из какого-то Проекции Доктора, но я не могу придумать умного обходного пути этого?

Любые предложения?

РЕДАКТИРОВАТЬ:

Я использую GraphQL.NET (graphql-dotnet) от Joe McBride версии 2.4.0

EDIT 2:

Либо я делаю это неправильно, либо я нене знаю.

В качестве одного из предложенных комментариев я скачал GraphQL.EntiПакет tyFramework Nuget от SimonCropp

Я выполнил все необходимые для этого настройки:

        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, он запрашивает базу данных для всей сущности (все поля).Никаких оптимизаций не делается из коробки из GraphQL, не имеет значения, возвращаете ли вы IQueryable или другую сущность, к которой вы обращаетесь.

Каждый вывод, который здесь сделан, мой, он не гарантирован на 100% верно

Итак, я создал группу методов-помощников, которые создают выражение выбора для использования в запросе LINQ.Помощники используют свойство resolver context.SubFields для получения необходимых полей.

Проблема в том, что для каждого уровня запроса вам нужны только листья, скажем, некоторые запросы "специализации" с "SpecializationName" и "Code"»и« Доктора »с их« Именем »и прочее.В этом случае в резольвере поля специализаций RootQuery вам потребуется только проекция сущности Specializations, то есть: SpecializationName и Code, а затем, когда будет получено все Doctors из поля "докторы" в SpecializationTypeконтекст резолвера имеет разные подполя, которые должны использоваться для проекции Doctor.

Проблема с вышесказанным заключается в том, что, когда вы используете пакеты запросов, я предполагаю, даже если вы не понимаете, что для поля Doctors в SpecializationType требуется SpecializationId, извлеченный в поле специализаций RootQuery.

Полагаю, я не очень хорошо объяснил, через что прошел.

Базовая линия - насколько я понимаю, мы должны динамически создавать селекторы, которые 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;
    }
}

Это сообщение стало довольно большим, извините за промежуток ..

1 Ответ

0 голосов
/ 29 января 2019

Для 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...