2

I need to dynamically filter data from the particular table depending on user's permissions. E.g. "normal" user can see only records assigned to him but admin can see all. I'm using ninject to create DB context per request and create context by passing additional user info to a constructor. Then I apply dynamic filtering (EF6) from EntityFramework-Plus extensions:

public MyDbContext(bool isAdmin, string userId) : this()
{
    if (!isAdmin)
    {
        this.Filter<MyTable>(table => table.Where(...));
    }
}

This solution works as expected i.e. calling methods like:

ctx.MyTable.Where(...)

Results in extra join declared in filter.

But behaves oddly when I'm using method Find(). I'm using SqlServer profiler to see what happens under the hood:

  1. I Create context as restricted (non-admin user) - calling Find() will result in extra WHERE statement corresponding to filter's lambda expression
  2. Then I create the context as admin (separate request) - calling Find() will result in the same SQL expression (i expect no extra SQL clauses).

AFAIK this has something to do with query caching since adding extra line to constructor seems to solve the problem:

public MyDbContext(bool isAdmin, string userId) : this()
{
    // this "solves" the problem
    QueryFilterManager.ClearQueryCache(this);

    if (!isAdmin)
    {
        this.Filter<MyTable>(table => table.Where(...));
    }
}

That looks like a big overkill and it doesn't bring me any closer to understanding the problem. So here are my questions:

  1. Why this problem does not affect Where() but affects Find()?
  2. Is there any cleaner way to solve this issue? I've read about Dynamic Filters library but it's no good for me as it works only in code first model (DB first here).
  3. Are there better concepts of filtering data basing on per-request data (like userId in my example)?

UPDATE

This is what my lambda expression looks like:

private Func<IQueryable<MyTable>, IQueryable<MyTable>> GetFilter(string userId)
    {
        return t => t
                .Where(c.DataScopes.Any(
                        x => x.AspNetGroups.Any(
                            ang => ang.AspNetUsers.Any(
                                anu => anu.Id == userId))));
    }

AspNetGroups is my custom table to group users. Data persmissions are assigned to users group.

Krzysztof
  • 498
  • 5
  • 23
  • 1
    Maybe because Where returns a List and Find a singleton? Can you fill in the `...` on the where and also show the Find()? – bommelding Jun 04 '18 at 09:43
  • Where() returns IQueryable not a list. As requested I've edited my question with filter code. – Krzysztof Jun 04 '18 at 09:50
  • May be this is some of the EF+ filter limitations, for instance [EF6 - Limitations - Context Filter](http://entityframework-plus.net/query-filter) section – Ivan Stoev Jun 04 '18 at 10:02
  • Im not sure if it make sense to put filter stuff in the constructor of the db Context, surely you can move that to get filter `private Func, IQueryable> GetFilter(string userId)` for a cleaner feel – Seabizkit Jun 04 '18 at 10:03
  • I still haven't got a clue about how you apply a filter to Find(). We are talking about `Find(id)`, right? And you could remove the mvc tag. – bommelding Jun 04 '18 at 10:03
  • @IvanStoev: this section of docs doesn't really help much. Should i assume from that that only global filters works as expected? – Krzysztof Jun 04 '18 at 10:18
  • @bommelding: yup it's Find(object id) and AFAIK it's auto applied by EFPlus (at the bottom level Find is still a select top 2 * from table so it just adds additional where clauses). At least it looks like it from what i have seen in my profiler. – Krzysztof Jun 04 '18 at 10:20
  • Right, I hadn't distilled EfPlus as separate product from the title. – bommelding Jun 04 '18 at 10:23

0 Answers0