koenbeuk / EntityFrameworkCore.Projectables

Project over properties and functions in your linq queries
MIT License
260 stars 17 forks source link

Index was outside the bounds of the array when using Expression-Bodied Properties #63

Closed buraktamturk closed 1 year ago

buraktamturk commented 1 year ago

Hello, first of all thanks for the amazing library!

Consider the following code

public class ProductController : ControllerBase {
        private bool IsAdmin => this.User.IsInRole("Admin");

        public async Task SomeMethod() {
             var products = await db.products
                  .Where(product => IsAdmin || product.enabled)
                  // ...
        }
}

Since IsAdmin is declared as a property, Entity Framework gets the value of the IsAdmin property in the client side and correctly sends the client-side evaluated value to the SQL query. Notice that IsAdmin does not have [Projectable] attribute, this is intentional.

The same code is broken when running on full compatibility mode of this library (but it works on limited compatibility mode). I am attaching the stack-trace of a more complex query.

    System.IndexOutOfRangeException: Index was outside the bounds of the array.
       at EntityFrameworkCore.Projectables.Extensions.TypeExtensions.GetOverridingProperty(Type derivedType, PropertyInfo propertyInfo)
       at EntityFrameworkCore.Projectables.Extensions.TypeExtensions.GetConcreteProperty(Type derivedType, PropertyInfo propertyInfo)
       at EntityFrameworkCore.Projectables.Services.ProjectableExpressionReplacer.VisitMember(MemberExpression node)
       at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
       at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
       at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node)
       at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
       at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
       at EntityFrameworkCore.Projectables.Services.ProjectableExpressionReplacer.VisitMethodCall(MethodCallExpression node)
       at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
       at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
       at EntityFrameworkCore.Projectables.Services.ProjectableExpressionReplacer.VisitMethodCall(MethodCallExpression node)
       at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
       at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
       at EntityFrameworkCore.Projectables.Services.ProjectableExpressionReplacer.VisitMethodCall(MethodCallExpression node)
       at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
       at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
       at EntityFrameworkCore.Projectables.Services.ProjectableExpressionReplacer.VisitMethodCall(MethodCallExpression node)
       at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
       at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
       at EntityFrameworkCore.Projectables.Services.ProjectableExpressionReplacer.VisitMethodCall(MethodCallExpression node)
       at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
       at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
       at EntityFrameworkCore.Projectables.Services.ProjectableExpressionReplacer.VisitMethodCall(MethodCallExpression node)
       at EntityFrameworkCore.Projectables.Infrastructure.Internal.CustomQueryCompiler.Expand(Expression expression)
       at EntityFrameworkCore.Projectables.Infrastructure.Internal.CustomQueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
       at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
       at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken)
       at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
       at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.CountAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)

Modifying the same code like following or running the library in limited compatibility mode fixes the problem.

public class ProductController : ControllerBase {
        private bool IsAdmin => this.User.IsInRole("Admin");

        public async Task SomeMethod() {
             var isAdmin = IsAdmin;
             var products = await db.products
                  .Where(product => isAdmin || product.enabled)
                  // ...
        }
}

My guess is in full compatibility mode the library tries to find the generated expression of IsAdmin property and fails, can it keep that part of the expression as it is (where [Projectable] attribute is not explicitly defined in the property) instead of failing like this?

Thanks for your work!

koenbeuk commented 1 year ago

This is actually a more generic issue where the use of non-public properties in a query would cause this exception to happen. Thanks for reporting it!