You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/types/unions.md
+24-23Lines changed: 24 additions & 23 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,11 +4,9 @@ title: Unions
4
4
sidebar_label: Unions
5
5
---
6
6
7
-
## Unions
8
-
9
7
Unions are an aggregate graph type representing multiple, different `OBJECT` types with no guaranteed fields or interfaces in common; for instance, `Salad` or `Bread`. Because of this, unions define no fields themselves but provide a common way to query the fields of the union members when one is encountered.
10
8
11
-
Unlike other graph types there is no concrete representation of unions. Where a `class` is an object graph type or a .NET enum is an enum graph type there is no analog for unions. Instead unions are virtual types that exist at runtime based their declaration site in a `GraphController`.
9
+
Unlike other graph types there is no concrete representation of unions. Where a `class` is an object graph type or a .NET `enum` is an enum graph type there is no analog for unions. Instead unions are semi-virtual types that are created from proxy classes that represent them at design time.
12
10
13
11
## Declaring a Union
14
12
@@ -56,17 +54,17 @@ query {
56
54
57
55
In this example we :
58
56
59
-
- Declared an action method named `RetrieveFood` with a field name of `searchFood`
60
-
- Declared a union on our graph named `SaladOrBread`
61
-
- Included two object types in the union: `typeof(Salad)` and `typeof(Bread)`
57
+
- Declared an action method named `RetrieveFood` with a graph field name of `searchFood`
58
+
- Declared a union type on our graph named `SaladOrBread`
59
+
- Included two object types in the union: `Salad` and `Bread`
62
60
63
61
Unlike with [interfaces](./interfaces) where the possible types returned from an action method can be declared else where, you MUST provide the types to include in the union in the declaration.
64
62
65
63
> Union member types must be declared as part of the union.
66
64
67
65
### What to Return for a Union
68
66
69
-
Notice we have a big question mark on what the action method returns in the above example. From a C# perspective, there is no `IFood` interface shared between `Salad` and `Bread`. This represents a problem for static-typed languages like C#. Since unions are virtual types there exists no common `System.Type` that you can return for generated data. `System.Object` might work but it tends to be too general and the runtime will reject it as a safe guard.
67
+
Notice we have a big question mark on what the action method returns in the above example. From a C# perspective, in this example, there is no `IFood` interface shared between `Salad` and `Bread`. This represents a problem for static-typed languages like C#. Since unions are virtual types there exists no common type that you can return for generated data. `System.Object` might work but it tends to be too general and the runtime will reject it as a safe guard.
70
68
71
69
So what do you do? Return an `IGraphActionResult` instead and let the runtime handle the details.
72
70
@@ -92,11 +90,11 @@ public class KitchenController : GraphController
92
90
}
93
91
```
94
92
95
-
Under the hood, GraphQL ASP.NET looks at the returned object type at runtime to evaluate the graph type reference then continues on in that scope with the returned value.
93
+
> Any controller action that declares a union MUST return an `IGraphActionResult`
96
94
97
95
## Union Proxies
98
96
99
-
If you need to reuse your unions in multiple methods you'll want to create a class that implements `IGraphUnionProxy`(or inherits from `GraphUnionProxy`) to encapsulate the details, then add that as a reference in your controller methods instead of the individual types. This can also be handy for uncluttering your code if you have a lot of possible types for the union. The return type of your method will still need to be `IGraphActionResult`. You cannot return a `IGraphUnionProxy` as a value.
97
+
In the example above we declare the union inline. But what if we wanted to reuse the `SaladOrBread` union in multiple places. You could declare the union exactly the same on each method or use a union proxy. Create a class that implements `IGraphUnionProxy` or inherits from `GraphUnionProxy` to encapsulate the details, then add that as a reference in your controller methods instead of the individual types. This can also be handy for uncluttering your code if you have a lot of possible types for the union. The return type of your method will still need to be `IGraphActionResult`. You cannot return a `IGraphUnionProxy` as a value.
100
98
101
99
```csharp
102
100
publicclassKitchenController : GraphController
@@ -107,24 +105,27 @@ public class KitchenController : GraphController
107
105
}
108
106
109
107
// SaladOrBread.cs
110
-
usingGraphQL.AspNet.Schemas.TypeSystem;
111
108
publicclassSaladOrBread : GraphUnionProxy
112
109
{
113
110
publicSaladOrBread()
114
-
: base(typeof(Salad), typeof(Bread))
115
-
{}
111
+
: base()
112
+
{
113
+
this.Name="SaladOrBread";
114
+
this.AddType(typeof(Salad));
115
+
this.AddType(typeof(Bread));
116
+
}
116
117
}
117
118
```
118
119
119
-
> You can create a union proxy by inheriting from `GraphUnionProxy` or directly implementing `IGraphUnionProxy`
120
+
> If you don't supply a name, graphql will automatically use the name of the proxy as the name of the union.
120
121
121
122
## Union Name Uniqueness
122
123
123
-
Union names must be unique in a schema. If you do declare a union in multiple action methods without a proxy, GraphQL will attempt to validate the references by name and included types. As long as all declarations are the same, that is the name and the set of types match, then there is no issue. Otherwise, a `GraphTypeDeclarationException` will be thrown.
124
+
Union names must be unique in a schema. If you do declare a union in multiple action methods without a proxy, GraphQL will attempt to validate the references by name and included types. As long as all declarations are the same, that is the name and the set of included types, then there graphql will accept the union. Otherwise, a `GraphTypeDeclarationException` will be thrown at startup.
124
125
125
126
## Liskov Substitutions
126
127
127
-
[Liskov substitutions](https://en.wikipedia.org/wiki/Liskov_substitution_principle) (the L in [SOLID](https://en.wikipedia.org/wiki/SOLID)) are an important part of object oriented programming and .NET. To be able to have one class masquerade as another allows us to easily extend our code's capabilities without any rework.
128
+
[Liskov substitutions](https://en.wikipedia.org/wiki/Liskov_substitution_principle) (the L in [SOLID](https://en.wikipedia.org/wiki/SOLID)) are an important part of object oriented programming. To be able to have one class masquerade as another allows us to easily extend our code's capabilities without any rework.
128
129
129
130
130
131
```csharp
@@ -188,15 +189,12 @@ query {
188
189
</div>
189
190
<br/>
190
191
191
-
Most of the time, GraphQL ASP.NET can correctly interpret which type it should match on to allow the query to progress. However, in the above example, we declare a union, `RollOrBread`, that is of types `Roll` and `Bread` yet we return a `Bagel` from the action method.
192
-
193
-
Since `Bagel` inherits from `Roll` and subsequently from `Bread` which type should we match against when executing the following query?
192
+
Most of the time, graphql can correctly interpret the correct union type of a returned data object and continue processing the query. However, in the above example, we declare a union, `RollOrBread`, that is of types `Roll` and `Bread` yet we return a `Bagel` from the action method.
194
193
195
-
The bagel is both the type `Roll`AND the type `Bread`, it could be used as either. GraphQL ASP.NET will be unable to determine which type to use and can't advance the query to select the appropriate fields. The query result is said to be indeterminate.
194
+
Since `Bagel` is both a `Roll`and `Bread` which type should graphql match against to continue executing the query? Since it could be either, graphql will be unable to determine which type to use and can't advance the query to select the appropriate fields. The query result is said to be indeterminate.
196
195
197
-
GraphQL ASP.NET offers a way to allow you to take control of your unions and make the determination on your own. The `ResolveType` method of `IGraphUnionProxy` will be called whenever a query result is indeterminate, allowing you to choose which of your UNION's allowed types should be used.
196
+
GraphQL ASP.NET offers a way to allow you to take control of your unions and make the determination on your own. The `MapType` method of `GraphUnionProxy` will be called whenever a query result is indeterminate, allowing you to choose which of your UNION's allowed types should be used.
198
197
199
-
> Note: `IGraphUnionProxy.ResolveType` is not based on the explicit value being inspected, but only on the `System.Type`. The results for a given field are cached for speedier type resolution on subsequent queries.
200
198
201
199
```csharp
202
200
// RollOrBread.cs
@@ -206,7 +204,7 @@ public class RollOrBread : GraphUnionProxy
@@ -226,8 +224,11 @@ public class BakeryController : GraphController
226
224
}
227
225
```
228
226
227
+
> Note: `MapType` is not based on the resolved field value, but only on the `System.Type`. This is by design to guarantee consistency in query execution.
228
+
> If your returned type causes the query to remain indeterminate a validation error (rule 6.4.3) will be applied to the query.
229
+
229
230
The query will now interpret all `Bagels` as `Rolls` and be able to process the query correctly.
230
231
231
232
If, via your logic you are unable to determine which of your Union's types to return then return null and GraphQL will supply the caller with an appropriate error message stating the query was indeterminate. Also, returning any type other than one that was formally declared as part of your Union will result in the same indeterminate state.
232
233
233
-
**Note:** Most of the time GraphQL ASP.NET will never call the `ResolveType` method on your UNION. If your union types do not share an inheritance chain, for instance, the method will never be called. If your types do share an inheritance chain, such as in the example above, considering using an interface graph type along with specific fragments instead of a UNION, to avoid the issue altogether.
234
+
**Note:** Most of the time GraphQL ASP.NET will never call the `TypeMapper` on your UNION. If your union types do not share an inheritance chain, for instance, the method will never be called. If your types do share an inheritance chain, such as in the example above, considering using an interface graph type along with specific fragments instead of a UNION, to avoid the issue altogether.
0 commit comments