Skip to content
This repository has been archived by the owner on Apr 5, 2019. It is now read-only.

Support filters like "document.Permisions.Any(p => p.UserId == userId)" #11

Open
gentledepp opened this issue Aug 11, 2014 · 9 comments

Comments

@gentledepp
Copy link

Hi!

I know you write in your documentation, that your solution does not support "Complex Join Logic"

However, I always have the very same join semantic like so:

modelBuilder.Conventions.Add(FilterConvention.Create<IResource, long>("UserId",
            (e, userId) => e.AccessPredicates.Any(@as => @as.UserId == userId)));

So every resource points to an AccessPredicate table which is just a table consisting of "ResourceId, UserId".

Would this scenario be hard to implement?
What steps would I have to follow?

@gentledepp
Copy link
Author

I started to debug your code.
In my scenario, I get the following error "Additional information: ParameterExpression of type 'Demo.Models.AccessPredicate' cannot be used for delegate parameter of type 'Demo.Models.Document'

thrown at:

    return Expression.Lambda<Func<TEntity, bool>>(body, node.Parameters[0]);

of your ParameterReplacer, even though I gave it a constant filterexpression:

modelBuilder.Conventions.Add(FilterConvention.Create<IResource, long>("UserId",
                //(e, userId) => e.AccessPredicates.Any(@as => @as.UserId == userId)));
                (e, userId) => e.AccessPredicates.Any(@as => @as.UserId == (long)2l)));

the expression tree is as follows:

.Lambda #Lambda1<System.Func`3[Demo.Models.IResource,System.Int64,System.Boolean]>(
    Demo.Models.IResource $e,
    System.Int64 $userId) {
    .Call System.Linq.Enumerable.Any(
        $e.AccessPredicates,
        .Lambda #Lambda2<System.Func`2[Demo.Models.AccessPredicate,System.Boolean]>)
}

.Lambda #Lambda2<System.Func`2[Demo.Models.AccessPredicate,System.Boolean]>(Demo.Models.AccessPredicate $as)
{
    $as.UserId == 2L
}

I am unfortunately new to this subject. Could you please give me any insights?
I can also post my demo project, if you wish.

@gentledepp
Copy link
Author

Ok... so now building the expression tree seems to work finally.
What I had to change was the ParameterReplacer.VisitLamda method, as it truis to always return a lamda expression of type Func<TEntity, bool>, but as I have a sub-lamda (as.UserId == 2l) I have to add a type-check:

           protected override Expression VisitLambda<T>(Expression<T> node)
            {
                var arg1 = typeof (T).GetGenericArguments().FirstOrDefault();
                if(arg1 != null && !arg1.IsAssignableFrom(typeof(TEntity)))
                    return base.VisitLambda<T>(node);

                var body = Visit(node.Body);
                return Expression.Lambda<Func<TEntity, bool>>(body, node.Parameters[0]);
            }

so now it blows up in the second last line of FilterQueryVisitor.Visit(...)

var output = result.Predicate.Accept(normalizer);

with the error: A first chance exception of type 'System.ArgumentOutOfRangeException' occurred in EntityFramework.dll

Additional information: No property with the name 'AccessPredicates' is declared by the type 'CodeFirstDatabaseSchema.Document'.

AccessPredicates is a navigationproperty on Document, however the EdmType (I debugged entity framework where the error is thrown: System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder.Internal.ArgumentValidation.ValidateProperty)
only has two items in its "Member" collection: "Id" and "Title"

However, if I do not use the filter, and write the query manually like so:

var x = ctx.Documents
                .Where(@d => @d.AccessPredicates.Any(@a => @a.UserId == userId))
                .Take(1).FirstOrDefault();

then the "Member" collection contains "Id", "Title", "AccessPredicates" and "Chapter" as I'd expect.
Do you have any idea how that can be?

It seems to me that EntityFramework constructs the StructuralTypes (which inherit EdmType)
for every query it parses and when I add my filter, I misses something... :/

@gentledepp
Copy link
Author

For ease of debugging, I added a test project: http://1drv.ms/1BbrBAL
Just run "update-database" and then hit F5.
I really hope you can help me on this one :/

@jbogard
Copy link
Owner

jbogard commented Aug 13, 2014

Sigh. So yeah, it turns out manipulating queries is actually really quite difficult. I don't know much more than you do to be honest, it was a lot of going through the EF source code when I made this, but I still don't understand everything going on, it's mostly undocumented.

@gentledepp
Copy link
Author

Hi! Posted this to EF team. Here is the answer: dotnet/efcore#515 (comment)

Do you understand anything from this?

Besides: Seems your EF.Filters applies the parameters of a parameterized filter BEFORE the query is cached. So the query is cached with the first parameter applied. Once I change the parameter (e.g. in another DbContext instance) the query in cache has still the old parameter and not its placeholder, so the new one is not applied. I have to explicitly disable query caching to avoid this. Any hints about that?

@jbogard
Copy link
Owner

jbogard commented Sep 25, 2014

Can you try the latest version now? There was a pull request to change to the CSpace, which I think helps here (no more caching)

@gentledepp
Copy link
Author

Oh thanks - seems I overlooked that.
I'll look into that. Thank you!

And do you know what is meant by the "IStoreModel" by dotnet/efcore#515 (comment) ?

Cite: "Investigation:
In order to filter with foreign key relationships you need to use the IStoreModelConvention. EntityFramework.Filters uses Configuration Conventions which doesn't know about Independent Associations (IAs) and therefore when you try filter on a foreign relationship it won't know what you are trying to access."

@jbogard
Copy link
Owner

jbogard commented Sep 26, 2014

Well, no, but I can dig more into it.

@gentledepp
Copy link
Author

No it's ok. Just thought you could understand this rather short hint of the EF team member. :)
I hope they will provide me with more insights.
Thanks anyway

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

No branches or pull requests

2 participants