Skip to content

$select doesn't work on fields of type IList<string> #1286

@anasik

Description

@anasik

Assemblies affected
ASP.NET Core OData 8.x
ASP.NET Core OData 9.x

Describe the bug
EF Core 8 comes with support for primtive collections in models.

Naturally, OData should pick up on that support. As of now, if you create a List<string> field in your model and then fetch all the records for that model, it works flawlessly. You see an array of strings in the response.

However, if you try to $select that field, it fails with the following error:

Click to expand error ``` System.NullReferenceException: Object reference not set to an instance of an object. at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.CreateGetValueExpression(ParameterExpression dbDataReader, Int32 index, Boolean nullable, RelationalTypeMapping typeMapping, Type type, IPropertyBase property) at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.VisitExtension(Expression extensionExpression) at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ProcessShaper(Expression shaperExpression, RelationalCommandCache& relationalCommandCache, IReadOnlyList`1& readerColumns, LambdaExpression& relatedDataLoaders, Int32& collectionId) at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.VisitExtension(Expression extensionExpression) at System.Linq.Expressions.ExpressionVisitor.VisitMemberAssignment(MemberAssignment node) at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node) at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection`1 nodes, Func`2 elementVisitor) at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node) at System.Linq.Expressions.ExpressionVisitor.VisitMemberAssignment(MemberAssignment node) at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node) at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection`1 nodes, Func`2 elementVisitor) at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node) at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ProcessShaper(Expression shaperExpression, RelationalCommandCache& relationalCommandCache, IReadOnlyList`1& readerColumns, LambdaExpression& relatedDataLoaders, Int32& collectionId) at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitShapedQuery(ShapedQueryExpression shapedQueryExpression) at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression) at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression) at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query) at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_0`1.b__0() at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken) at System.Text.Json.Serialization.Converters.IAsyncEnumerableOfTConverter`2.OnWriteResume(Utf8JsonWriter writer, TAsyncEnumerable value, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.Serialization.JsonCollectionConverter`2.OnTryWrite(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.Serialization.JsonConverter`1.WriteCore(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.SerializeAsync(Stream utf8Json, T rootValue, CancellationToken cancellationToken, Object rootValueBoxed) at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.SerializeAsync(Stream utf8Json, T rootValue, CancellationToken cancellationToken, Object rootValueBoxed) at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.SerializeAsync(Stream utf8Json, T rootValue, CancellationToken cancellationToken, Object rootValueBoxed) at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|30_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context) ```

Reproduce steps
Run a $select on any IList field.

Data Model

public class TestPrimitiveCollections
    {
        [Key]
        public string Key { get; set; }
        public ICollection<string>? ListTestString { get; set; }
        public IList<bool>? ListTestBool { get; set; }
        public IList<int>? ListTestInt { get; set; }
        public IList<double>? ListTestDouble { get; set; }
        public IList<float>? ListTestFloat { get; set; }
        public IList<DateTime>? ListTestDateTime { get; set; }
        public IList<DateOnly>? ListTestDateOnly { get; set; }
        public IList<Uri>? ListTestUri { get; set; }
        public uint[]? ListTestUint { get; set; }

    }

EDM (CSDL) Model

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
    <edmx:DataServices>
        <Schema Namespace="Models.SRE" xmlns="http://docs.oasis-open.org/odata/ns/edm">
            <ComplexType Name="Property">
                <Property Name="Key" Type="Edm.String" Nullable="false" />
                <Property Name="ListTestString" Type="Collection(Edm.String)" />
                <Property Name="ListTestBool" Type="Collection(Edm.Boolean)" Nullable="false" />
                <Property Name="ListTestInt" Type="Collection(Edm.Int32)" Nullable="false" />
                <Property Name="ListTestDouble" Type="Collection(Edm.Double)" Nullable="false" />
                <Property Name="ListTestFloat" Type="Collection(Edm.Single)" Nullable="false" />
                <Property Name="ListTestDateTime" Type="Collection(Edm.DateTimeOffset)" Nullable="false" />
                <Property Name="ListTestDateOnly" Type="Collection(Edm.Date)" Nullable="false" />
                <Property Name="ListTestUri" Type="Collection(System.Uri)" />
                <Property Name="ListTestUint" Type="Collection(Edm.Int64)" Nullable="false" />
                <NavigationProperty Name="Member" Type="Models.SRE.Member" />
                <NavigationProperty Name="OpenHouses" Type="Collection(Models.SRE.OpenHouse)" />
            </ComplexType>
        </Schema>
    </edmx:DataServices>
</edmx:Edmx>

Request/Response
http://localhost:5270/odata/mls/Properties?select=ListTestString

Expected behavior
The following output:

[

    {
        "ListTestString": [
            "I",
            "Was",
            "Lost"
        ]
    },
    {
        "ListTestString": [
            "I",
            "Am",
            "Lost"
        ]
    },
    {
        "ListTestString": [
            "I",
            "Am",
            "sda"
        ]
    }
]

Screenshots

Additional context
his happens because in SelectExpandBinder.ProjectAsWrapper(), we check if it's a collection, and if yes, we wrap it as a collection rather than an element. This works perfectly fine for all collections except for strings.

In my model, I have collection fields for a number of primitive types out there. None of them crash. Uri doesn't fully work but I will report that separately.

That's because queries of the form: Entity().Select( d=> d.StringListProperty.Select(d=> d))) just inherently don't work. It appears to be a bug in EF Core that one can easily recreate without even needing to initialize OData.

Furthermore, I also upgraded my project to use the under-preview dotnet 9.0 with EF Core 9.0, hoping that the issue would have been fixed by now but had no luck.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingfollowup

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions