当前位置: 首页 > 知识库问答 >
问题:

当使用接口和内联片段时,如何解决GraphQL中的正确类型

林冥夜
2023-03-14

我面临一个问题,我需要从__resolveType内部引用父级上已解析的字段。不幸的是,我需要引用的字段不是作为父级的原始api响应的一部分,而是来自另一个字段解析器,虽然这并不重要,但确实如此,所以它是未定义的。

但是我需要这些字段(在本例中;obj.barCountobj.bazCount)才能进行以下查询,所以我遇到了死胡同。我需要他们是可用的,以便我可以使用他们来确定什么类型解析的情况下,这个字段被定义。

这里有一个例子:

我希望能够进行的graphql查询:

{
  somethings { 
    hello
    ... on HasBarCount {
      barCount
    }
    ... on HasBazCount {
      bazCount
    }
  }
}

模式:

type ExampleWithBarCount implements Something & HasBarCount & Node {
  hello: String!
  barCount: Int
}

type ExampleWithBazCount implements Something & HasBazCount & Node {
  hello: String!
  bazCount: Int
}

interface Something {
  hello: String!
}

interface HasBarCount {
  barCount: Int
}

interface HasBazCount {
  bazCount: Int
}

解析程序:

ExampleWithBarCount: {
  barCount: (obj) => {
    return myApi.getBars(obj.id).length || 0
  }
}

ExampleWithBazCount {
  bazCount: (obj) => {
    return myApi.getBazs(obj.id).length || 0
  }
}

问题:

Something: {
  __resolveType(obj) {
    console.log(obj.barCount) // Problem: this is always undefined
    console.log(obj.bazCount) // Problem: this is always undefined

    if (obj.barCount) {
      return 'ExampleWithBarCount';
    }

    if (obj.bazCount) {
      return 'ExampleWithBazCount';
    }

    return null;
  }
}

有其他解决方案的想法吗?或者我遗漏了什么?

下面是关于用例的更多信息。

在数据库中,我们有一个表“实体”。这个表非常简单,只有真正重要的列是id、parent_id、name. type,然后您当然可以将一些额外的元数据附加到它。

与“实体”一样,类型是从后端管理系统中动态创建的,然后您可以将类型分配给您的具体实体。

“实体”的主要目的是通过父实体id和不同的“类型”(在实体的“类型”列中)建立嵌套实体的层次结构/树。会有一些不同的元数据,但我们不要集中于此。

注意:实体可以命名为任何东西,类型可以是任何东西。

在API中,我们有一个endpoint,在这里我们可以获取具有特定类型的所有实体(旁注:除了实体上的单一类型之外,我们还有一个endpoint,可以通过其分类/术语获取所有实体)。

在第一个实现中,我通过在开发过程中添加UX'er规范中的所有“已知”类型对模式进行建模。实体树可以类似于。

  • 公司(或组织、...、公司...等)
    • 分支机构(或地区,等)
    • 工厂(或建筑物、设施等)
      • 区域(或房间,等)

      但是这种等级制度只是一种方法。每个的命名可能完全不同,您可能会将其中一些向上或向下移动一个级别,或者根本没有它们,这取决于用例。

      唯一确定的是,它们共享同一个html" target="_blank">数据库表,将定义类型列/字段,它们可能有子项,也可能没有子项。层次结构中的底层没有子级,而是机器。其余的只是不同的元数据,我认为我们应该忽略它们,以避免进一步复杂化。

      正如您所看到的,层次结构需要非常灵活和动态,因此我意识到这不是一个很好的解决方案。

      在这种情况下,在最低级别的“区域”中,需要有一个“机器”字段,该字段应该返回一个机器列表(它们在数据库的“机器”表中,不是层次结构的一部分,只是与“entity_id”相关在“机器”桌上。

      在上面的层次结构中,我有所有的模式类型和解析器:组织、分支、工厂、区域等等,但我大部分只是重复我自己,所以我想我可以转向接口来尝试更一般化。

      因此,与其这样做

      {
        companies{
          name
          branchCount
          buildingCount
          zoneCount
          branches {
            name
            buildingCount
            zoneCount
            buildings {
              name
              zoneCount
              zones {
                name
                machines {
                  name
                } 
              }
            }
          }
        }
      }
      

      必须为实体的所有不同名称添加模式/解析器,我认为这会起作用:

      {
        entities(type: "companies") { 
          name
          ... on HasEntityCount {
            branchCount: entityCount(type: "branch")
            buildingCount: entityCount(type: "building")
            zoneCount: entityCount(type: "zone")
          }
          ... on HasSubEntities {
            entities(type: "branch") {
              name
              ... on HasEntityCount {
                buildingCount: entityCount(type: "building")
                zoneCount: entityCount(type: "zone")
              }
              ... on HasMachineCount {
                machineCount
              }
              ... on HasSubEntities {
                entities(type: "building") {
                  name
                  ... on HasEntityCount {
                    zoneCount: entityCount(type: "zone")
                  }
                  ... on HasMachineCount {
                    machineCount
                  }
                  ... on HasSubEntities {
                    entities(type: "zone") {
                      name
                      ... on HasMachines {
                        machines
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
      

      接口为:

      interface HasMachineCount {
        machineCount: Int
      }
      
      interface HasEntityCount  {
        entitiyCount(type: String): Int
      }
      
      interface HasSubEntities {
        entities(
          type: String
        ): [Entity!]
      }
      
      interface HasMachines {
        machines: [Machine!]
      }
      
      interface Entity {
        id: ID!
        name: String!
        type: String!
      }
      

      下面的方法可行,但我确实希望避免使用带有大量可选/空字段的单一类型:

      type Entity {
        id: ID!
        name: String!
        type: String!
        # Below is what I want to avoid, by using interfaces
        # Imagine how this would grow
        entityCount
        machineCount
        entities
        machines
      }
      

      在我自己的逻辑中,我不关心实体被称为什么,只关心期望的字段。我想避免使用一个实体类型,它上面有很多可为空的字段,所以我认为接口或联合会有助于将事情分开,所以我最终使用了HasSubEntities、HasEntityCount、HasMachineCount和HasMachines,因为底部实体下面没有实体,只有底部实体才有机器。但在实际代码中,可能会有比2多得多的内容,如果我认为没有以某种方式利用接口或联合,那么最终可能会有很多可选字段。

共有1个答案

阴雪风
2023-03-14

这里有两个不同的问题。

第一,GraphQL以自顶向下的方式解析字段。父字段总是在任何子字段之前解析。因此,永远不可能从父字段的解析器(或“同级”字段的解析器)访问字段解析为的值。对于具有抽象类型的字段,这也适用于类型解析程序。在调用任何子解析程序之前,将解析字段类型。解决此问题的唯一方法是将相关逻辑从子解析器移动到父解析器内部。

第二,假设某物字段具有类型某物(或[某物],等等)。),您试图运行的查询将永远不会工作,因为HasBarCountHasBazCount不是东西。当您告诉GraphQL一个字段有一个抽象类型(接口或联合)时,您是说该字段返回的可能是几个对象类型之一,这些对象类型将在运行时缩小到恰好一个对象类型。可能的类型要么是组成联合的类型,要么是实现接口的类型。

联合只能由对象类型组成,不能由接口或其他联合组成。类似地,只有对象类型可以实现接口--其他接口或联合可能不实现接口。因此,当使用带有返回抽象类型的字段的内联片段时,这些内联片段的on条件将始终是对象类型,并且必须是所讨论的抽象类型的可能类型之一。

因为这是伪代码,所以不太清楚您正试图用这种模式对哪些业务规则或用例进行建模。但我可以说,通常不需要创建接口并让类型实现它,除非您计划在模式中添加一个将该接口作为其类型的字段。

编辑:在高层次上,听起来你可能只是想做这样的事情:

type Query {
  entities(type: String!): [Entity!]!
}

interface Entity {
  type: String!
  # other shared entity fields
}

type EntityWithChildren implements Entity {
  type: String!
  children: [Entity!]!
}

type EntityWithModels implements Entity {
  type: String!
  models: [Model!]!
}

类型解析程序需要检查我们是否有模型,因此您需要确保在获取实体时获取相关模型(而不是在模型解析程序中获取它们)。或者,您可以向数据库中添加某种列,将实体标识为层次结构中的“最低”实体,在这种情况下,您可以使用此属性。

function resolveType (obj) {
  return obj.models ? 'EntityWithModels' : 'EntityWithChildren'
}

现在,您的查询如下所示:

entities {
  type
  ... on EntityWithModels {
    models { ... }
  }
  ... on EntityWithChildren {
    children {
      ... on EntityWithModels {
        models { ... }
      }
      ... on EntityWithChildren {
        # etc.
      }
    }
  }
}

由于实体名称的可变性和层次结构深度的可变性,计数有点棘手。我建议让客户机在从服务器获取整个图形后计算出计数。如果确实要添加计数字段,则必须具有类似于childrenCount孙子数count等字段。然后正确填充这些字段的唯一方法是从根位置获取整个图形。

 类似资料:
  • 我试图在我的项目中实现这段代码,但不起作用RecycerView:如何捕捉ImageView上的onClick?: 我实现了一个接口来处理RecycerView中的onclick imageview和onclick row。问题是,当我试图在imageview上或在行中单击时,不会发生任何事情。如果我在我的ViewHolder类中尝试同样的方法,效果会很好,因为我可以看到祝酒词。问题是我需要这个工

  • 我有4个片段,其中一个片段中有一个viewpager和一个不同的类,我用3个其他片段定义了viewpager的适配器,第一次打开此片段时,所有子片段都正确显示在viewpager中,但当我切换(使用transaction.replace)到另一个片段并再次返回时,子片段消失了,我不能使用ChildFragmentManager,因为它在代码中显示错误(ChildFragmentManager无法解

  • 我有一个简单的POST请求,它需要一个json内容类型头和一个类似 当我在Postman中运行它时,它按预期运行,返回200个状态和预期的响应。 以下是同样的空手道脚本: 当我运行它时,它返回{“code”:“415”,“status”:“Unsupported Media Type”}。控制台输出显示在POST期间设置了正确的内容类型。 即使我在脚本中特别设置了内容类型,415仍然返回例如。 或

  • 我正在尝试通过将数据与接口传递给片段来将数据从对话片段获取到片段。到目前为止,我知道首先是因为它的片段,我需要在MainActive上实现接口,并从那里将数据发送到我想要的任何片段。我明白,但我不知道如何做到这一点。到目前为止,我已经在MainActive中实现了接口,我正在获取数据,但我不知道如何将其发送到片段。 主要活动 对话片段-此对话片段从画廊/相机获取图片 片段-此片段应从dialogf

  • 我有这个活动,它包含一个片段。这个片段布局由一个包含多个片段(实际上是两个)的视图寻呼机组成。 当创建视图分页器时,它的适配器被创建,被调用,我的子片段被创建。太棒了。 现在,当我旋转屏幕时,框架处理片段的重新创建,适配器从主片段在我的中再次创建,但是从未被调用,因此我的适配器持有错误的引用(实际上为空),而不是两个片段。 我发现片段管理器(即子片段管理器)包含一个名为的片段数组,这当然是代码无法