Open agriffard opened 4 years ago
Hi, thanks for reporting this.
It looks like one of the expression visitors extracting what fields you're trying to access from the schema doesn't like the DateTime.UtcNow
part. What you could do to at least make it work is this:
var now = DateTime.UtcNow;
var query = client.CreateQuery(s => s.WeatherForecast(
new WeatherForecastWhereInput() { CreatedUtcGt = now }));
I can have a look later today for more general fix on this. (In about 4 hours from now)
Thank you for your answer.
Can you please tell me how a And
works?
This code:
var startDateUtc = DateTime.Today.ToUniversalTime();
var endDateUtc = startDateUtc.AddDays(1);
var query = client.CreateQuery<IEnumerable<WeatherForecast>>(s => s.WeatherForecast(
new WeatherForecastWhereInput() { And = new List<WeatherForecastWhereInput>() {
new WeatherForecastWhereInput() { CreatedUtcGte = startDateUtc },
new WeatherForecastWhereInput() { CreatedUtcLt = endDateUtc } }
}));
returns this NotImplementedException: GetValueFromExpression: unknown NodeType: ListInit
at Telia.GraphQL.Client.PathGatheringVisitor.GetValueFromExpression(Expression argument)
at Telia.GraphQL.Client.PathGatheringVisitor.GetValueFromMemberInit(MemberInitExpression argument)
at Telia.GraphQL.Client.PathGatheringVisitor.GetValueFromExpression(Expression argument)
at Telia.GraphQL.Client.PathGatheringVisitor.<GetArgumentsFromMethod>d__9.MoveNext()
at System.Linq.Enumerable.Count[TSource](IEnumerable`1 source)
at Telia.GraphQL.Client.ChainLink.Equals(Object obj)
at Telia.GraphQL.Client.SelectionChainGrouping.<>c__DisplayClass3_0.<TryGroup>b__0(ChainLink e)
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
at Telia.GraphQL.Client.SelectionChainGrouping.TryGroup(ChainLink part, List`1 groupedLink)
at Telia.GraphQL.Client.SelectionChainGrouping.Group()
at Telia.GraphQL.GraphQLCLient`1.CreateOperation[TType,TReturn](Expression`1 selector, QueryContext context, OperationType operationType)
at Telia.GraphQL.GraphQLCLient`1.CreateQuery[TReturn](Expression`1 selector)
In general the library looks at everything you put into the expression and tries to inline it into a GraphQL query. So it actually crawls the expression tree, recomposes it into its own data model and then it optimizes the query.
For example if you use the same field twice in the query then it merges it into one. So this:
var q = client.CreateQuery(s => s.WeatherForecast().Select(e => new
{
x = e.Author,
y = $"{e.Owner}/{e.Author}"
}));
Will result into:
{
field0: weatherForecast{
field0: author
field1: owner
__typename
}
__typename
}
Since it identified that the Author
field is pointing to the same data it won't duplicate it in the query but when it maps it back to your C# types it will distribute it accordingly.
When it comes to input objects they are a bit tricky with lots of corner cases. In your case List constructor inside an input is not supported. Array initialization is.
So if you would use array initialization like this:
var query = client.CreateQuery<IEnumerable<WeatherForecast>>(s => s.WeatherForecast(
new WeatherForecastWhereInput()
{
And = new WeatherForecastWhereInput[] {
new WeatherForecastWhereInput() { CreatedUtcGte = startDateUtc },
new WeatherForecastWhereInput() { CreatedUtcLt = endDateUtc } }
}));
It should yield you this query:
{
field0: weatherForecast(where: {and: [{createdUtc_gte: "2020-07-12T22:00:00Z"}, {createdUtc_lt: "2020-07-13T22:00:00Z"}]}){
contentItemId
contentItemVersionId
contentType
displayText
published
latest
modifiedUtc
publishedUtc
createdUtc
owner
author
render
temperature
summary
__typename
}
__typename
}
Sadly it won't right now because of another probably recently introduced bug. I'm contemplating about just doing JSON serialization on input objects in general and passing them in the result query as variables, which is probably what I'll end up doing. That should solve whole lot of problems. I'll add that on my list.
Ok, thank you for the explanations.
Right now with an Array, it results to an InvalidCastException:
Object cannot be stored in an array of this type.
at System.Array.InternalSetValue(Void* target, Object value)
at Telia.GraphQL.Client.PathGatheringVisitor.GetValueFromNewArrayInit(NewArrayExpression argument)
at Telia.GraphQL.Client.PathGatheringVisitor.GetValueFromExpression(Expression argument)
at Telia.GraphQL.Client.PathGatheringVisitor.GetValueFromMemberInit(MemberInitExpression argument)
at Telia.GraphQL.Client.PathGatheringVisitor.GetValueFromExpression(Expression argument)
at Telia.GraphQL.Client.PathGatheringVisitor.<GetArgumentsFromMethod>d__9.MoveNext()
at System.Linq.Enumerable.Count[TSource](IEnumerable`1 source)
at Telia.GraphQL.Client.ChainLink.Equals(Object obj)
at Telia.GraphQL.Client.SelectionChainGrouping.<>c__DisplayClass3_0.<TryGroup>b__0(ChainLink e)
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
at Telia.GraphQL.Client.SelectionChainGrouping.TryGroup(ChainLink part, List`1 groupedLink)
at Telia.GraphQL.Client.SelectionChainGrouping.Group()
at Telia.GraphQL.GraphQLCLient`1.CreateOperation[TType,TReturn](Expression`1 selector, QueryContext context, OperationType operationType)
at Telia.GraphQL.GraphQLCLient`1.Query[TReturn](Expression`1 selector)
Hi, sorry for the delay. I finally got around to implement hopefully all the necessary fixes.
For your initial query:
var query = client.CreateQuery<IEnumerable<WeatherForecast>>(s => s.WeatherForecast(
new WeatherForecastWhereInput() { CreatedUtcGt = DateTime.UtcNow }));
It now creates not string but rather an object of type GraphQLQueryInfo containing the query itself and all variables passed in. Instead of trying to inline the input values into the query it just evaluates them and puts the resulting value in as a variable. So if you would create something like this:
var client = new Client();
var query = client.CreateQuery(s => s.WeatherForecast(
new WeatherForecastWhereInput() { CreatedUtcGt = DateTime.UtcNow }));
var json = JsonConvert.SerializeObject(query, new JsonSerializerSettings()
{
Converters = new List<JsonConverter>()
{
new GraphQLObjectConverter() // in order to maintain the same naming of input values this converter needs to be added
},
Formatting = Formatting.Indented
});
It will result into the following JSON:
{
"query": "query Query($var_0: WeatherForecastWhereInput) {\r\n field0: weatherForecast(where: $var_0){\r\n contentItemId\r\n contentItemVersionId\r\n contentType\r\n displayText\r\n published\r\n latest\r\n modifiedUtc\r\n publishedUtc\r\n createdUtc\r\n owner\r\n author\r\n render\r\n temperature\r\n summary\r\n __typename\r\n }\r\n __typename\r\n}",
"variables": {
"var_0": {
"createdUtc_gt": "2020-07-28T08:06:36.0050729Z"
}
}
}
Similar thing will happen with this query:
var query = client.CreateQuery<IEnumerable<WeatherForecast>>(s => s.WeatherForecast(
new WeatherForecastWhereInput()
{
And = new List<WeatherForecastWhereInput>() {
new WeatherForecastWhereInput() { CreatedUtcGte = startDateUtc },
new WeatherForecastWhereInput() { CreatedUtcLt = endDateUtc } }
}));
Which results into:
{
"query": "query Query($var_0: WeatherForecastWhereInput) {\r\n field0: weatherForecast(where: $var_0){\r\n contentItemId\r\n contentItemVersionId\r\n contentType\r\n displayText\r\n published\r\n latest\r\n modifiedUtc\r\n publishedUtc\r\n createdUtc\r\n owner\r\n author\r\n render\r\n temperature\r\n summary\r\n __typename\r\n }\r\n __typename\r\n}",
"variables": {
"var_0": {
"and": [
{
"CreatedUtcGte": "2020-07-27T22:00:00Z"
},
{
"CreatedUtcLt": "2020-07-28T22:00:00Z"
}
]
}
}
}
This required some additional type info to be added into the schema cs file so it knows what kind of graphql type it is you're trying to send in as that variable. So to use these fixes you might want to download the latest version of https://marketplace.visualstudio.com/items?itemName=MarekMagdziak.Telia-GraphQL-Tooling&ssr=false#overview
Version 1.1.40
of the NuGet package and the extension contains all the fixes.
Edit: Just found out that Newtonsoft JSON converts enums as integers as default so you might want to also add new StringEnumConverter
to your converter list.
OK, about the enum serialiazation, when I am querying this:
var result = client.Query<IEnumerable<WeatherForecast>>(s => s.WeatherForecast(
new WeatherForecastWhereInput()
{
And = new WeatherForecastWhereInput[] {
new WeatherForecastWhereInput() { CreatedUtcGte = startDateUtc },
new WeatherForecastWhereInput() { CreatedUtcLt = endDateUtc } }
},
new WeatherForecastOrderByInput() { CreatedUtc = OrderByDirection.ASC })
);
OrderByDirection
being an enum:
[GraphQLType("OrderByDirection")]
public enum OrderByDirection
{
ASC,
DESC
}
It sends a query like this:
{"query":"query Query($var_0: WeatherForecastWhereInput, $var_1: WeatherForecastOrderByInput) {\r\n field0: weatherForecast(where: $var_0, orderBy: $var_1){\r\n contentItemId\r\n contentItemVersionId\r\n contentType\r\n displayText\r\n published\r\n latest\r\n modifiedUtc\r\n publishedUtc\r\n createdUtc\r\n owner\r\n author\r\n render\r\n date\r\n temperature\r\n summary\r\n __typename\r\n }\r\n __typename\r\n}","variables":{"var_0":{"AND":[{"createdUtc_gte":"2020-07-27T22:00:00Z"},{"createdUtc_lt":"2020-07-30T22:00:00Z"}]},"var_1":{"createdUtc":0}}}
Note the last variable:
"var_1":{"createdUtc":0}
should be
"var_1":{"createdUtc":"ASC"}
When I try to create a query with a DateTime GraphQLField, an error occurs.
Example with a schema like this one:
If I try to call a query like this one:
A NullReferenceException occurs: Object reference not set to an instance of an object