В настоящее время я изучаю разработку 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;
}
}
Это сообщение стало довольно большим, извините за промежуток ..