Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
anasik opened this issue Jul 18, 2024 · 2 comments · Fixed by #1282
Closed

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

anasik opened this issue Jul 18, 2024 · 2 comments · Fixed by #1282
Assignees
Labels
bug Something isn't working followup

Comments

@anasik
Copy link

anasik commented Jul 18, 2024

Assemblies affected
ASP.NET Core OData 8.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.

@anasik anasik added the bug Something isn't working label Jul 18, 2024
@anasik
Copy link
Author

anasik commented Jul 18, 2024

I want to add that I have already submitted a PR that fixes this. Sadly, I'm not able to request a review from anyone. I thought maybe opening an issue would help with getting that kind of attention so here I am.

@julealgon
Copy link
Contributor

@anasik

I want to add that I have already submitted a PR that fixes this.

Can you add a "Fixes" link from the PR to the issue then?

WanjohiSammy pushed a commit that referenced this issue Nov 3, 2024
* Checking for primitive collection in SelectExpandBinder.ProjectAsWrapper. This gets rid of the excetion resulting from selecting a List<String> field.
@anasik anasik closed this as completed Nov 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working followup
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants