efcore icon indicating copy to clipboard operation
efcore copied to clipboard

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

Open maumar opened this issue 1 year ago • 1 comments

Scenario:

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

        return AssertQuery(
            async,
            ss => from e in ss.Set<PrimitiveCollectionsEntity>()
                  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<string> value3;
    int? value4;
    value1 = (int?)dataReader.GetInt32(0);
    value2 = dataReader.IsDBNull(1) ? default(bool?) : (bool?)dataReader.GetBoolean(1);
    value3 = dataReader.IsDBNull(2) ? default(IQueryable<string>) : (IQueryable<string>)(IEnumerable<string>)new JsonCollectionOfReferencesReaderWriter<string[], string>(JsonStringReaderWriter.Instance).FromJsonString(
        json: (string)dataReader.GetFieldValue<object>(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:

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.

maumar avatar Apr 27 '24 04:04 maumar

FYI @roji This might go away if/when we get rid of nav expansion, also low priority bug, only happens in legacy mode and test case seems uncommon

maumar avatar Apr 27 '24 04:04 maumar