Pull to refresh

Выполнение запросов GraphQL с помощью OdataToEntity

Reading time3 min
Views3.2K


В статье рассказывается как транслировать запросы 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 и его реализации для:


  1. Entity Framework OeEf6DataAdapter
  2. Entity Framework Core OeEfCoreDataAdapter
  3. 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.

Tags:
Hubs:
+1
Comments3

Articles