
В статье рассказывается как транслировать запросы GraphQL в OData и выполнять их написав
совсем немного кода на C#.
Как это работает
Основная идея проекта — трансляция запросов GraphQL в OData, трансляция запросов OData в дерево выражений, которое затем транслируется в запрос для ORM. Разбор запроса GraphQL и сериализация его результатов осуществляется с помощью GraphQL for .NET. Разбор запроса OData осуществляется с помощью OData .NET Libraries. Трансляция запросов (GraphQL -> OData -> expression tree) и их выполнение осуществляется с помощью OdataToEntity.
Непосредственный доступ к данным осуществляется через ORM выполняющее полученное дерево выражений (expression tree). Выполнение запросов на различных ORM осуществляется через абстрактный класс OeDataAdapter и его реализации для:
- Entity Framework OeEf6DataAdapter
- Entity Framework Core OeEfCoreDataAdapter
- Linq2Db OeLinq2DbDataAdapter
От пользователя требуется лишь иметь контекст доступа к данным (EF/EF Core — DbContext, Linq2Db — DataConnection).
Более подробно о выполнении запросов OData можно прочитать в моей предыдущей статье OdataToEntity легкий способ создания .Net Core OData сервисов.
Пример использования
Для примера будет использоваться схема Star Wars, ORM EF Core, провайдер SQLite in memory.
Сначала надо создать контекст доступа к данным StarWarsContext. Затем адаптер доступа к данным StarWarsDataAdapter. После можно начать выполнять запрос:
String query = @" { human(id: ""1"") { name friends { name appearsIn { name } } } } "; //create data adapter var dataAdapter = new StarWars.StarWarsDataAdapter(false, "test"); //build odata model IEdmModel edmModel = dataAdapter.BuildEdmModelFromEfCoreModel(); //create graphql query parser var parser = new OeGraphqlParser(edmModel); //get graphql result ExecutionResult result = await parser.Execute(query); //serialize json String json = new DocumentWriter(true).Write(result); Console.WriteLine(json);
Запрос GraphQL:
{ human(id: ""1"") { name friends { name appearsIn { name } } } }
Транслируется в OData:
Human?$filter=Id eq '1'&$select=Name&$expand=Friends($select=Name;$expand=AppearsIn($select=Name))
Транслируется в SQL:
SELECT "h"."Name" AS "Item1", "h"."Id" AS "Item2", CASE WHEN "t"."Id" IS NULL THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END, "t"."Name" AS "Item10", "t"."Id" AS "Item20", CASE WHEN "EpisodeEnum"."Value" IS NULL THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END, "EpisodeEnum"."Name" AS "Item11", "EpisodeEnum"."Value" AS "Item21" FROM "Hero" AS "h" LEFT JOIN "HeroToHero" AS "CharacterToCharacter" ON "h"."Id" = "CharacterToCharacter"."CharacterId" LEFT JOIN ( SELECT "Hero".* FROM "Hero" AS "Hero" WHERE "Hero"."CharacterType" IN (1, 2) ) AS "t" ON "CharacterToCharacter"."FriendId" = "t"."Id" LEFT JOIN "HeroToEpisode" AS "CharacterToEpisode" ON "t"."Id" = "CharacterToEpisode"."CharacterId" LEFT JOIN "Episodes" AS "EpisodeEnum" ON "CharacterToEpisode"."EpisodeId" = "EpisodeEnum"."Value" WHERE ("h"."CharacterType" = 1) AND ("h"."Id" = @__Item1_0)
Результат JSON:
{ "data": { "human": [ { "name": "Luke", "friends": [ { "name": "R2-D2", "appearsIn": [ { "name": "NEWHOPE" }, { "name": "EMPIRE" }, { "name": "JEDI" } ] }, { "name": "C-3PO", "appearsIn": [ { "name": "NEWHOPE" }, { "name": "EMPIRE" }, { "name": "JEDI" } ] } ] } ] } }
Генерируемый SQL не имеет проблемы N+1 запросов, все данные получаются в одном запросе.
Структура исходного кода
Исходный код разделен на две части: в папке source — сама библиотека и сборки доступа к различным источникам данных, в папке test — тесты и примеры кода.
Сама библиотека находится в папке source/OdataEntity.GraphQL.
Тесты test/OdataToEntity.Test.GraphQL.
Файл солюшена sln/OdataToEntity.Test.GraphQL.sln.
