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

Fix the LINQ injection to make the real support for AspNet.Core.OData in the ABP #19749

Open
antonGritsenko opened this issue May 8, 2024 · 1 comment

Comments

@antonGritsenko
Copy link

ABP Framework >= 6.0

Some issues mentioned "support" of the OData (like this or this), but the real app doesn't work.

The problem is extended properties and injection made into the LINQ by ABP.

Image code like this (using DBContext just for simplicity):

public class UsersController : ODataController
{
    private readonly MyDbContext _dbContext;
    private readonly IObjectMapper _mapper;
    private readonly ODataConventionModelBuilder _builder;


    public UsersController(MyDbContext dbContext, IObjectMapper mapper)
    {
        _dbContext = dbContext;
        _mapper = mapper;

        _builder = new ODataConventionModelBuilder();
        _builder.EntityType<IdentityUser>()
            .HasName("IdentityUser")
            .HasKey(t => t.Id);
        _builder.EntityType<IdentityRole>()
            .HasName("IdentityRole")
            .HasKey(t => t.Id);

        _builder.EntitySet<IdentityUser>("Users");
        _builder.EntitySet<IdentityUser>("Roles");

    }

    public IQueryable<IdentityUserDto> Get(ODataQueryOptions<IdentityUserDto> query)
    {
        

        var model = _builder.GetEdmModel();

        IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Users");
        ODataPath path = new ODataPath(new EntitySetSegment(entitySet));

        var enttyOpts = new ODataQueryContext(model, typeof(IdentityUser), path);

        var opts = new ODataQueryOptions<IdentityUser>(enttyOpts, query.Request);

        var queryable = (IQueryable<IdentityUser>)opts.ApplyTo(_dbContext.Users.AsQueryable<IdentityUser>());
        var result = queryable.ToList();
        return _mapper.Map<List<IdentityUser> ,List<IdentityUserDto>>(result).AsQueryable();
    }
}

That will work and you even can do a very simple query like $filter=name eq 'John'. But as soon as you want to use any "extended" (from ABP point of view) properties like UserName (I have no idea why it counts it as Extended, to be honest) you will get error like this:

The LINQ expression 'DbSet()\r\n .Where(i => __ef_filter__p_0 || !(EF.Property(i, "IsDeleted")) && __ef_filter__p_1 || (Guid?)EF.Property(i, "TenantId") == __ef_filter__CurrentTenantId_2)\r\n .Where(i => (string)i.ExtraProperties.ContainsKey("UserName") ? i.ExtraProperties["UserName"] : null == __TypedProperty_0)' could not be translated. Additional information: Translation of method 'System.Collections.Generic.Dictionary<string, object>.ContainsKey' failed. If this method can be mapped to your custom function, see https://go.microsoft.com/fwlink/?linkid=2132413 for more information. 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.g__CheckTranslated|15_0(ShapedQueryExpression translated, <>c__DisplayClass15_0& )
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.Query.Internal.QueryCompiler.<>c__DisplayClass9_01.<Execute>b__0() at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1.GetEnumerator() at System.Collections.Generic.List1..ctor(IEnumerable1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source)
at CommunityManager.OData.UsersController.Get(ODataQueryOptions`1 query)
at lambda_method2483(Closure , Object , Object[] )
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

@antonGritsenko
Copy link
Author

Connected with #11566 and #10662

OData should somehow use this instead ExtraProperties:

var query = (await GetQueryableAsync()).Where(u => EF.Property<string>(u, "SocialSecurityNumber") == "123");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant