36

我有一个 GraphQL 服务器,它能够为指定的源(例如,传感器数据)提供时间序列数据。获取传感器数据的示例查询可能是:

query fetchData {
    timeseriesData(sourceId: "source1") {
      data {
        time
        value
      }
    }
}

在我的前端,我希望允许用户选择 1 个或多个来源并显示一个图表,其中每个来源都有一条线。使用这样的查询似乎可以做到这一点:

query fetchData {
    series1: timeseriesData(sourceId: "source1") {
      data {
        time
        value
      }
    }
    series2: timeseriesData(sourceId: "source2") {
      data {
        time
        value
      }
    }
}

大多数 GraphQL 教程似乎都关注静态查询(例如,唯一改变的是变量,而不是请求的实际形状) - 但在我的情况下,我需要查询本身是动态的(每个都有一个 timeseriesData 请求我选择的 ID)。

我有以下限制:

  1. 修改服务器的架构不是一种选择(例如,我不能将一组 ID 传递给解析器)
  2. 我的查询是使用模板字符串指定的,例如 gql`...`
  3. 我不想手动将查询构建为字符串,因为这似乎是灾难的根源,并且意味着我失去了所有工具优势(例如自动完成、语法突出显示、linting)

我正在使用的堆栈是:

  • Apollo 客户端(特别是 apollo-angular)
  • 打字稿
  • graphql-tag(用于定义查询)

理想情况下,我想要做的是有某种方式将两个查询合并为一个,这样我就可以按照第一个示例定义它们,然后在抽象层中将它们连接在一起,这样我就可以像第二个示例一样获得单个查询通过电线发送。

但是我不确定如何实现这一点,因为 graphql-tag 正在将查询解析为 AST,我正在努力理解以这种方式操作查询是否可行。

有什么技术可以生成这样的动态查询,而查询的形状是未知的?

4

4 回答 4

16

您可以使用片段来定义公共字段,以及该片段上的变量绑定@include(if: Boolean)@skip(if: Boolean)指令,以获取在执行时已知的动态字段。

根据规范,最好避免手动字符串插值来构造动态查询。

指令1可以根据作为查询变量传递的布尔表达式来包含或跳过字段。指令可以附加到字段或片段包含,并且可以以服务器所需的任何方式影响查询的执行。

query Hero($episode: Episode, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
      name
    }
  }
}

在变量中:

{
  "episode": "JEDI",
  "withFriends": false
}

当然,您可以像在第二个示例中那样发送命名查询。客户端将自动批处理请求。

于 2019-02-23T06:35:41.660 回答
6

我认为当用户动态选择传感器甚至您不知道开发时(不是运行时)的传感器时,您别无选择,只能使用字符串功能。

const mainQuery = gql
  `query fetchData($sourceId: String!) {
    timeseriesData(sourceId: $sourceId) {
      data {
        time
        value
      }
    }
  }`;

const mainQueryStr = mainQuery.loc.source.body;

mainQueryStr是您查询的字符串值(用于处理问题的动态性)然后循环传感器并替换$sourceId为每个传感器的 id

// You have to remove the query wrapper first
// Then replace sensor id
const sensorsQueries = sensors.map(sid => mainQueryStr
  .split(`\n`)
  .slice(1, 7)
  .replace(`$sourceId`, sid)
)

然后你应该加入 sensorQueries 并进行新的 GraphQL 查询

const finalQuery = gql`
  query fetchData {
    ${sensorsQueries.join(`\n`)}`
  };

在这种情况下,您可以使用诸如自动完成、语法高亮和 ... 等工具优势用于mainQuery查询,而不是finalQuery(因为您是动态创建的)

于 2020-03-16T08:06:26.737 回答
5

我认为您可以为此使用片段!"queries"但是在这种情况下你仍然必须写 2 fragments

fragment首先让我们为每个创建一个timeSeries,请检查您的 timeSeries 查询类型,我将其称为timeseriesDataQuery

const series1Q = gql`
  fragment series1 on timeseriesDataQuery {
    series1: timeseriesData(sourceId: "source1") {
      data {
        time
        value
      }
    }
  }
}

const series2Q = gql`
  fragment series2 on timeseriesDataQuery {
    series2: timeseriesData(sourceId: "source2") {
      data {
        time
        value
      }
    }
  }
}

然后在查询中将它们拼接起来:

export const mainQuery = gql`
    query fetchData {
      ...series1 
      ...series2
    }
    ${series1Q}
    ${series2Q}
`    
于 2018-07-13T10:41:33.937 回答
1

请记住,查询只是一个字符串。您可以使用简单的模板文字来实现您的动态查询。

const generateQuery = () => {
  let query = ""

  for (let i = 1; i < 3; i++) {
    const series = `series${i}`
    const source = `source${i}`

    query += `
      ${series}: timeseriesData(sourceId: "${source}") {
        prices
        queried
      }
    `
  }

  return query
}

const fetchDataQuery = gql`
  query fetchData {
    ${generateQuery()}
  }
`
于 2021-09-29T06:09:39.673 回答