В настоящее время я изучаю разработку 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) Джо Макбрайда версии 2.4.0.
Обновлено еще раз:
Либо я делаю это неправильно, либо я не знаю.
Как предложил один из комментариев, я скачал пакет GraphQL.EntityFramework 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. Помощники используют свойство context.SubFields преобразователя для получения необходимых полей.
Проблема в том, что вам нужны для каждого уровня запроса только листья, скажем некоторые запросы "специализации" с "SpecializationName" и "Код" и "Врачи" с их "Имя" и прочее. В этом случае в распознавателе поля специализации 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;
}
}
Этот пост стал довольно большим, извините за размах ..
Какую библиотеку GraphQL вы используете? GraphQL.NET является распространенным, но не Только. Без GraphQL.EntityFramework нет прямого сопоставления с EF.
Вы имеете в виду, что GraphQL.EntityFramework обрабатывает генерацию запроса SQL SELECT, соответствующего запрошенным полям запроса GraphQL?
Я буду читать документацию GraphQL.EntityFramework. Спасибо, что указали путь.





Для 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.
Этот код явно запрашивает столбцы Id, Name в запросе EF. Он не использует запрос GraphQL, чтобы решить, какие поля возвращать.
@PanagiotisKanavos, ты прав. Я использовал этот способ для управления сгенерированным запросом NHLStatsQuery. Есть ли лучший способ контролировать сгенерированный запрос GraphQL?
Я предлагаю тебе:
1-использовать модели dto и сопоставлять их с моделями базы данных
Это означает, что вам нужно преобразовать входную модель dto в модель базы данных для сохранения в db; а также конвертировать модели базы данных, полученные из базы данных Entity Framework, в модель dto.
Это классический подход, используемый при создании общего API, который, например, получает данные модели dto во входном запросе, преобразует dto для сохранения данных в базе данных и наоборот.
Модель 2-map dto для типов graphql (тип objectgraph и тип inputobjectgraph)
Это означает, что для каждой модели dto может потребоваться запись 1 типа графа объекта и 1 типа графа входного объекта.
ДЛЯ ЭТОГО Я СОЗДАЛ АВТОМАТИЧЕСКИЙ КОНВЕРТЕР DTO В GRAPHTYPE, поэтому вам не нужно писать K и K кодов!! (см ссылку в конце)
3-НЕ ИСПОЛЬЗУЙТЕ ADDDBCONTEXT! Промежуточное ПО Graphql использует одноэлементный шаблон; все, что используется через внедрение зависимостей в graphql, является одноэлементным извне, даже если оно зарегистрировано как ограниченное (AddDbContext означает «ограниченный»).
Это означает, что у вас открыто 1 соединение для запуска. Вы не можете выполнять 2 операции db одновременно!
В реальной жизни вы не можете использовать AddDbContext с Graphql!
Для этого можно использовать заводской шаблон. Итак, не передавайте dbcontext в инъекции зависимостей, а используйте Func и явно создайте экземпляр dbcontext.
Вот полный пример реализации: https://github.com/graphql-dotnet/graphql-dotnet/issues/576#issuecomment-626661695
@jeremylikness рассказал о GraphQL с EF Core 6 на конференции .NET 2021. Я бы рекомендовал использовать .NET 6 и ознакомиться с его выступлением:
https://devblogs.microsoft.com/dotnet/get-to-know-ef-core-6/#graphql
https://thewikihow.com/video_GBvTRcV4PVA
https://thewikihow.com/video_4nqjB_z5CU0
Вот пример реализации с использованием сервера Hot Chocolate GraphQL:
https://chillicream.com/docs/hotchocolate/integrations/entity-framework
Вот что Microsoft написала о GraphQL для EF Core 6.0 в своем плане высокого уровня:
GraphQL has been gaining traction over the last few years across a variety of platforms. We plan to investigate the space and find ways to improve the experience with .NET. This will involve working with the community on understanding and supporting the existing ecosystem. It may also involve specific investment from Microsoft, either in the form of contributions to existing work or in developing complimentary pieces in the Microsoft stack.
https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-6.0/plan#graphql
Как выглядит конфигурация и сопоставление Ядро EF? Как вы загружаете данные?