Pull to refresh

Comments 28

Интересно было бы почитать как именно реализовывалось, наглядная демонстрация работы с Expression-ами.

Спасибо за комментарий. Если интересно покопаться в исходниках, вот исходный код Visitor'а, который строит Where-выражение. После рефакторинга получился совсем худенький - https://github.com/RDavydenko/SmartGraphQLClient/blob/master/src/SmartGraphQLClient.Core/Visitors/WhereExpressionVisitor/WhereExpressionVisitor.cs

Постараюсь в ближайшее время написать про работу с Expression'ами в этом проекте. Ничего сильно сложного здесь нет, но есть неочевидные подводные камни, с которыми пришлось столкнуться

Труд, конечно, объемный, но, то, что вы реализовали это вовсе не LINQ. Потому что LINQ работает в принципе не так. В нем вы не повторяете API System.Linq.Queryable, а реализуете только свой System.Linq.IQueryProvider который транслирует уже готовый expression в запрос к источнику данных провайдером для которого он является. А построением самого этого expression занимается уже готовый класс-расширение System.Linq.Queryable, который от конкретного провайдера не зависит. Статью все равно плюсанул :)

Согласен. Немного кликбейтный получился заголовок. Да, я не задавался целью реализовать свой собственный QueryProvider, хотел просто сделать похожий на Linq api, чтобы было удобно и привычно пользоваться таким GraphQL-клиентом. Под капотом здесь своя реализация некоторого IQueryable (IGraphQLQueryable), ничего общего с обычным Linq он не имеет

А почему так? Через QueryProvider же по камушкам все.

Потому что далеко не все доступные методы-расширения Linq подходят для GraphQL-запроса. Мы, например, не можем выполнить какие-нибудь Average, Sum, Aggregate, AsNoTracking и т.д. GraphQL-сервер от ChilliCream без расширений на стороне сервера поддерживает только фильтрацию, сортировку, проекцию, пагинацию и FirstOrDefault, поэтому проще сделать свой интерфейс по типу IQueryable только для GraphQL, чтобы однозначно определить методы, которые точно будет поддерживаться. Иначе пришлось бы из большей половины Linq-методов кидать исключения, что они не поддерживаются

Только AsNoTracking как раз не является стандартным, он вообще не в классе Queryable находится. И как раз его-то при желании можно и поддержать (например, безопасно проигнорировать).


А вот с остальным согласен.

Это штатная для LINQ ситуация. Точно так же не каждый LINQ-запрос может быть оттранслирован в SQL. В этом случае провайдер просто кидает exception.

Вообще-то, LINQ — это встроенный в C# синтаксис запросов, тот который from … select …. И ему не важно какой так тип данных для построения запроса используется.

Это все все равно транслируется во fluent API из System.Linq.Queryable (который, по факту, все и используют - ни разу не видел чтобы кто-то реально использовал from ... select синтаксис).

И ему не важно какой так тип данных для построения запроса используется.

Ровно до тех пор, пока этот тип данных реализует IQueryable.

ни разу не видел чтобы кто-то реально использовал from … select синтаксис

Ну вот я его часто использую, особенно когда надоедает парные скобки выискивать...


Ровно до тех пор, пока этот тип данных реализует IQueryable.

Хотите сказать, что его нельзя применить ни к IEnumerable, ни к EntityQuery из WCF RIA Services? Советую попробовать и убедиться что всё прекрасно работает.

что его нельзя применить ни к IEnumerable

Можно. Только он опять-таки оттранслируется в расширения, только уже из System.Linq.Enumerable и, соответственно, работать будет несколько по-другому. Про WCF RIA, вот, реально не знаю, потому что видел его только раз и то краем глаза.

Я не говорю о том как оно будет работать. Я говорю о том, что синтаксис linq транслируется в вызовы методов независимо от того где эти методы объявлены, и это работает для любого класса. Главное — чтобы методы были видимы.

from ..select отлично подходит, когда надо использовать join

UFO just landed and posted this here

По идее было бы лучше избежать текстовых значений. Банальная опечатка проблемы создаст.

Вынесите в класс с константами и проблем не будет.

Если схема генерируется на базе SDL

Схема не генерируется. Не добавлял такой функциональности. В принципе, и так полно инструментов, которые генерируют классы из GraphQL-схемы.

Сделать типизированный класс-Endpoint - хорошая идея, мне нравится. Только я бы через конструктор тогда уж сохранял строку, а не через атрибут. В проекте есть возможность добавить атрибут [GraphQLEndpoint("users")] на сущность, но надо понимать, что одна и та же сущность может использоваться разных методах с разными endpoint'ами, поэтому лучше либо передавать строки, либо сделать такой типизированный класс-endpoint.

Определить интерфейсы и сделать что-то типа

Include<IRoles>() .ThenInclude<IUsers>()

или даже

Include<IRoles, IUsers>()

Не вижу смысла, т.к. это уже не похоже на Linq. Цель была - сделать удобный для всех инструмент взаимодействия с GraphQL-сервером. Чтобы все, кто умеет работать с Linq на примере того же EntityFramework, без проблем поняли, что и зачем тут происходит.

Часто вместе идут, можно сахара добавить SkipAndTake(5, 10)

Первый раз такое вижу, но если кому-то сильно надо, то можно же написать свой собственный метод-расширения.

Можно ещё добавить собственный анализатор, чтобы в compile time ловить не поддерживаемые expressions. Иначе определённый тип ошибок только в runtime будет вылезать.

В compile time - это, конечно, круто

Вынесите в класс с константами и проблем не будет.

Ещё как будут. Смотрите:


client.Query<CarModel>(Constants.Users)

Вроде и в константу вынесено что нужно, да вот что-то работать не будет… Надо не класс с константами делать, а аналог DbContext, для начала — хотя бы вот такой:


class QueryContext {
    private readonly IGraphQLClient client;

    public QueryContext(IGraphQLClient client) {
        this.client = client;
    }

    public Query<UserModel> Users => client.Query<UserModel>("users");
}

Ну от ошибок и опечаток никто не застрахован. Идея с QueryContext мне нравится ?

UFO just landed and posted this here

Понял теперь о чем речь. Ну я думаю, что большинство функционала, который не является стандартным в том же Linq, и здесь можно оставить на откуп пользователя. Кому понадобится что-то необычное - добавит свой extension себе локально. В комментариях уже были примеры со SkipAndTake , в принципе, и это можно реализовать, если кому-то понадобится

UFO just landed and posted this here

Ну и самое главное, что если в схеме что-то изменится, при перегенерации QueryContext эти изменения сломают компиляцию, что очень сильно поможет в избежании проблем.

Да, я задумывался о том, чтобы не возиться со строками, чтобы при компиляции ловить ошибки, т.к. клиент все-таки типизированный. Но еще можно сделать инфраструктурный Nuget-пакет с выходными моделями, и в нужном сервисе подключать его. Обновится Nuget-пакет - если изменились модели, будет ошибка на этапе компиляции

Но для этого, конечно, нужно следить за актуальностью версии Nuget-пакета в проекте

А можно вопрос - как реализуются в GraphQL пермишены. Типо не все поля или не все типы можно возвращать для каких-то пользователей.

Permission'ы - т.е. доступ на основе Permission'ов нужно делать на стороне GraphQL-сервера. Думаю, в сети можно найти примеры, погуглив что-то типа "PermissionBased Authorization HotChocolate".

Чтобы возвращать не все поля - тут посложнее будет, т.к. GraphQL-схема штука статичная и должна быть заранее известна при обращении к ней. Самый простой метод - это создать DTO и предоставить пользователю метод, возвращающий, например, список таких DTO. Тогда у пользователя не будет доступа до полей, которые ему не нужно видеть.

Можно и в рантайме вычищать ответ от GraphQL-сервера, ставля в null, например, какие-то поля, до которых у пользователя нет доступа, но, если честно, мне это не нравится. Нужно будет тогда еще и в фильтрацию, сортировку залазить, чтобы по недоступным полям выкидывать из запроса фильтрацию/сортировку. В общем, те еще костыли с велосипедами

Добавлю еще один вариант. Можно на стороне сервера возвращать заранее отфильтрованную/отсортированную IQueryable<>, например. Например:

public IQueryable<Entity> GetMyEntities(
  [Service] IUnitOfWork unitOfWork, 
  [Service] IUserAccessor userAccessor)
{
    var userId = userAccessor.GetCurrentUserId();
    return unitOfWork.Entities.Where(x => x.CreateUserId == userId);
}

Класс, то что нужно чтобы в graphql ходить из .net. Пара вопросов:

  • чем-то можно классы из gql-схемы сгенерить? Ну, чтобы не руками писать.

  • что с перформансом? Получается как-нибудь строки с запросами кэшировать, или хотя-бы прям в utf8 сразу писать?

1) При помощи этой библиотеки - нет, но должна быть точно не одна библиотека для генерации классов из схемы.
Можно посмотреть в сторону StrawberryShake - https://chillicream.com/docs/strawberryshake/v13/tooling

2) Пока кэширования не предусмотрено, надо подумать в эту сторону. Кстати, хорошая идея, чтобы добавить возможность запрос и при помощи строк для большей вариативности, хоть я и хотел от этого уйти, но лишним не будет, если появится

По производительности могу добавить, что ничего сверхъестественного здесь нет - для строк используется StringBuilder, для запросов в graphql-серверу - обычный HttpClient, а для десериализации ответа - System.Text.Json

По поводу 1 - хорошо бы, если было бы решение вместе с генерацией. Не обязательно ее делать в рамках самой библиотеки, но хорошо бы проверить что с каким-то сторонним генератором это работает.

Т.е. полноценное решение я вижу как-то так:

  • качаете GQL схему (в виде результата introspection query) в файл, кладете его в репу

  • запускаете команду из пакета X, у вас получается набор cs-файлов под эту схему

  • подключаете твою библиотеку

  • можете ходить в нужный GQL-API, с автокомплитом в IDE, строго-типизированно

По поводу 2 - может быть про перформанс - это чисто страхи, и на деле плодить строчки на каждый запрос - не так накладно. Может быть, если померить, там и не страшно. Но, как идеи для оптимизации:

  • уметь писать запрос сразу в сокет (минуя построение большой UTF-16 строки в памяти, и конвертацию ее дальше в UTF-8 - как теперь делает тот же встроенный JSON-сериализатор)

  • сделать некие pre-build queries - возможность написать (параметризированный) запрос, вызвать ему .Build(), и положить в какую-нибудь static-переменную.

Sign up to leave a comment.

Articles