dotnet / dotnet/efcore

Query: element access on a collection of primitives parameter throws bad exception when run on sql server that doesn't support JSON

Open
#33,621 1 comment 0 reactions 0 assignees View on GitHub
area-primitive-collections area-query
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

Scenario:

```
string[] values = new[] { "one", "two", "three", "four" };

return AssertQuery(
async,
ss => from e in ss.Set()
let value = e.Id > 0 && e.Id < 4 ? values[e.Id] : "zero"
select new { e.Id, value });
```

when ran in the "legacy" mode, we throw

```
Unable to cast object of type 'System.String[]' to type 'System.Linq.IQueryable`1[System.String]'.
```

Here is the shaper that we generate:

```
(queryContext, dataReader, resultContext, resultCoordinator) =>
{
int? value1;
bool? value2;
IQueryable value3;
int? value4;
value1 = (int?)dataReader.GetInt32(0);
value2 = dataReader.IsDBNull(1) ? default(bool?) : (bool?)dataReader.GetBoolean(1);
value3 = dataReader.IsDBNull(2) ? default(IQueryable) : (IQueryable)(IEnumerable)new JsonCollectionOfReferencesReaderWriter(JsonStringReaderWriter.Instance).FromJsonString(
json: (string)dataReader.GetFieldValue(2),
existingObject: null);
value4 = (int?)dataReader.GetInt32(0);
return new {
Id = (int)value1,
value = value2 == True ? value3
.ElementAt((int)value4) : "zero"
};
}
```

and here is the sql:

```sql
exec sp_executesql N'SELECT [p].[Id], CASE
WHEN [p].[Id] > 0 AND [p].[Id] < 4 THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END, @__values_0
FROM [PrimitiveCollectionsEntity] AS [p]',N'@__values_0 nvarchar(4000)',@__values_0=N'["one","two","three","four"]'
```

values parameter is array of strings, and we do read it as IEnumerable initially, but the ElementAt method that we use to get the element we need is on IQueryable, so we try convert to IQueryable and ultimately fail.

In preprocessing we normalize indexer over parameter array into:
`[ParameterQueryRootExpression].AsQueryable().ElementAt(...)`, then nav expansion gobbles up AsQueryable call (because in normal mode this all will get translated, so AsQueryable is not necessary). In legacy mode we end up client-evaling the whole thing, but we don't restore AsQueryable or apply original indexer or Enumerable.ElementAt.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.