Quantcast
Channel: Odata filtering in expanded property does not work - Stack Overflow
Viewing all articles
Browse latest Browse all 2

Odata filtering in expanded property does not work

$
0
0

We have created a .NET Core API which uses Odata to filter, select, and expand the data. The data is stored in a Microsoft SQL Server database and retrieved through EntityFramework Core (code first). We use Linq projection so the Odata filter is applied directly to the query, but this gives an error in the following situation:

When retrieving a list of results, e.g. authors expanded with books, everything works fine. It gives an error when filtering inside the expanded books e.g.: https://localhost:44316/odata/authors?$expand=Books($filter=Id eq 1)

    System.InvalidOperationException: The LINQ expression 'DbSet<Book>()    .Where(b0 => EF.Property<Nullable<int>>(EntityShaperExpression:         EntityType: Author        ValueBufferExpression:             ProjectionBindingExpression: EmptyProjectionMember        IsNullable: False    , "Id") != null && object.Equals(        objA: (object)EF.Property<Nullable<int>>(EntityShaperExpression:             EntityType: Author            ValueBufferExpression:                 ProjectionBindingExpression: EmptyProjectionMember            IsNullable: False        , "Id"),         objB: (object)EF.Property<Nullable<int>>(b0, "AuthorId")))    .Where(b0 => b0        .ToDto().Id == __TypedProperty_1)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.<VisitMethodCall>g__CheckTranslated|15_0(ShapedQueryExpression translated, <>c__DisplayClass15_0& )   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.TranslateSubquery(Expression expression)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberAssignment(MemberAssignment memberAssignment)   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberInit(MemberInitExpression memberInitExpression)   at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberAssignment(MemberAssignment memberAssignment)   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberInit(MemberInitExpression memberInitExpression)   at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Translate(SelectExpression selectExpression, Expression expression)   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateSelect(ShapedQueryExpression source, LambdaExpression selector)   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)   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.<ExecuteAsync>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 Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadInternal[T](Object value)   at Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsyncEnumerable(ActionContext context, ObjectResult result, Object asyncEnumerable, Func`2 reader)   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_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.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

I already created an issue on the odata github page, but it only partially solved our problem and we got no reaction on the second comment.

This is the code that we use to map from database model to DTO.

public static IQueryable<AuthorDTO> ProjectTo(IQueryable<Author> source){    return source?.Select(ProjectToAuthorDto());}private static Expression<Func<Author, AuthorDTO>> ProjectToAuthorDto(){    return author => new AuthorDTO    {        Firstname = author.Firstname,        Lastname = author.Lastname,        Id = author.Id,        Books = author.Books.Select(book => book.ToDto())    };}public static BookDTO ToDto(this Book book){    return ProjectToBookDto().Compile().Invoke(book);}private static Expression<Func<Book, BookDTO>> ProjectToBookDto(){    return book => new BookDTO    {        AuthorId = book.AuthorId,        Author = book.Author.ToDto(),        Id = book.Id,        ISBN = book.ISBN,        Title = book.Title    };}

When I perform the mapping inline, everything works fine (see image below), but this is not a solution as the mappings need to be reusable.enter image description here

This problem only occurs with Odata in combination with Linq projection. When we remove the Odata packages, everything is returned as expected. Furthermore, when we execute the query before returning the result (by adding .ToList()), we do get the expected result, however, the odata filter is not applied to the query. We have this problem in .NET Core 3.1 as well as in .NET 5.I created an extremely simplified minimal version of our problem in this repo.

We ran out of ideas and do not know what to try next. I hope that anyone got an idea to get the filter to work.

Thanks in advance!

Edit

I reworked the helper as Svyatoslav Danyliv suggested.

public static IQueryable<AuthorDTO> ProjectTo(IQueryable<Author> source){    return source?.Select(item => item.ToDto());}[Computed]public static AuthorDTO ToDto(this Author author){    return new AuthorDTO    {        Firstname = author.Firstname,        Lastname = author.Lastname,        Id = author.Id,        Books = author.Books.Select(book => book.ToDto())    };}[Computed]public static BookDTO ToDto(this Book book){    return new BookDTO    {        AuthorId = book.AuthorId,        //Author = book.Author.ToDto(),        Id = book.Id,        ISBN = book.ISBN,        Title = book.Title    };}

And call it via:

// Convert to DTOIQueryable<AuthorDTO> result = CustomMapper.ProjectTo(authors);return Ok(result.Decompile());

The error is gone, but now the result is truncated:

Truncated response

I also see in SQL Server Profiler that the query is not executed anymore when I apply the $filter.

when I use .DecompileAsync() I get the following error:

System.InvalidOperationException: The LINQ expression '$it' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberAssignment(MemberAssignment memberAssignment)   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberInit(MemberInitExpression memberInitExpression)   at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)   at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)   at System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor)   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitConditional(ConditionalExpression conditionalExpression)   at System.Linq.Expressions.ConditionalExpression.Accept(ExpressionVisitor visitor)   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberAssignment(MemberAssignment memberAssignment)   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberInit(MemberInitExpression memberInitExpression)   at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberAssignment(MemberAssignment memberAssignment)   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberInit(MemberInitExpression memberInitExpression)   at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Translate(SelectExpression selectExpression, Expression expression)   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateSelect(ShapedQueryExpression source, LambdaExpression selector)   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)   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.<ExecuteAsync>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 DelegateDecompiler.EntityFrameworkCore.AsyncDecompiledQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)   at Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadInternal[T](Object value)   at Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsyncEnumerable(ActionContext context, ObjectResult result, Object asyncEnumerable, Func`2 reader)   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_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.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Any ideas? Thanks again!

Edit 2

Svyatoslav Danyliv suggested to use the code below and that fixed the error above!

[EnableQuery(HandleNullPropagation = HandleNullPropagationOption.False]

Thank you for your effort!

enter image description here

Final (working) code:

public static IQueryable<AuthorDTO> ProjectTo(IQueryable<Author> source){    return source?.Select(item => item.ToDto());}[Computed]public static AuthorDTO ToDto(this Author author){    return new AuthorDTO    {        Firstname = author.Firstname,        Lastname = author.Lastname,        Id = author.Id,        Books = author.Books.Select(book => book.ToDto())    };}      [Computed]public static BookDTO ToDto(this Book book){    return new BookDTO    {        AuthorId = book.AuthorId,        //Author = book.Author.ToDto(),        Id = book.Id,        ISBN = book.ISBN,        Title = book.Title    };}

And call it with:

IQueryable<AuthorDTO> result = CustomMapper.ProjectTo(authors);return Ok(result.DecompileAsync());

Viewing all articles
Browse latest Browse all 2

Latest Images

Trending Articles





Latest Images