0

Given a DbContext and a ClientContext (custom session data about the user) is it possible create a DbContext that is "authorised": where only a subset of the rows on each "table" is available?

With an authorised DbContext I'm trying to have a central row-level authorisation implementation.

I've researched it a bit and the only way to filter out a DbSet would be to use something like Queryable.Where but that returns an IQueryable<T> and there doesn't seem to be a way to return a filtered DbSet<T> (except maybe for global queries that you can setup in Startup but they don't have access to injected dependencies like ClientContext).

Is it possible to define DbSet<T> authorisation filters via an injected scoped dependency like ClientContext?

10
  • Short answer: no. As you said, global query filters (not "global queries") aren't suitable because they're static in one db model. Commented Apr 24, 2020 at 8:35
  • @GertArnold see this issue - it clearly shows that you could use injected stuff with global query filters as they use instance properties of DbContext. Commented Apr 24, 2020 at 8:42
  • Please clarify this question by showing some examples of how/what you'd like to filter. If you always want to filter on pre-defined attributes, maybe global query filters can be your friend. Commented Apr 24, 2020 at 8:45
  • @GertArnold As I said the only requirement is to be able to inject dependencies somehow and be able to apply filters based on the scoped ClientContext. Commented Apr 24, 2020 at 9:29
  • Yeah, but don't you understand the dilemma? With global query filters you won't be able to filter on things you didn't pre-configure. Commented Apr 24, 2020 at 9:32

2 Answers 2

2

There are model-level query filters: https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.0#model-level-query-filters

From the link:

This feature allows LINQ query predicates (a boolean expression typically passed to the LINQ Where query operator) to be defined directly on Entity Types in the metadata model (usually in OnModelCreating). Such filters are automatically applied to any LINQ queries involving those Entity Types, including Entity Types referenced indirectly, such as through the use of Include or direct navigation property references.

Example from the link:

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    public int TenantId { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>().HasQueryFilter(
            p => !p.IsDeleted
            && p.TenantId == this.TenantId);
    }
}

You can use this for simple scenarios. You define an instance property in your DbContext and in OnModelCreating you specify HasQueryFilter on any entity you want to filter. The property is an instance property, so if you have a scoped DbContext, the correct property value from that request would be used, which is handy if you want to filter by something from your UserContext. I have personally never tried this so I don't know how complex it allows your implementation to be, but you can play with it.

Sign up to request clarification or add additional context in comments.

9 Comments

As OP said, global query filters don't fit the bill.
@GertArnold He said global queries that you can setup in Startup but they don't have access to injected dependencies like ClientContext and this can't apply to global query filters, because one can set up global query filters to use injected stuff.
So probably the question isn't clear enough to be answered. What I understand is that OP wants to filter in ways that can't be predicted (= pre-configured), but maybe you're right.
@GertArnold Yes, it can't be pre-configured. Filters depend on ClientContext which is a scoped injected dependency. You can't inject services via DbContext constructors, right?
@ShoeDiamente why wouldn't you be able to?
|
0

I'm not sure about EF and EF core, but we abstract the DbContext away into functional specific 'logic' blocks.

e.g:

class DbContext()
{
   public DbSet<PeopleEntity> peoples;
}
class PeopleLogic()
{
   DbContext _context;

   PeopleLogic(DbContext context)
   {
       _context = context;
   }

   IEnumerable GetAllPeoples()
   {
      // create context,
      // apply filters
      // return result
   }
}

We ofcourse have a base for simple CRUD operations;

public void AddOrUpdate(){
            lock (SyncDatabaseWriteObject)
            {
                try
                {
                    using (var context = CreateContext())
                    {
                        //insert the entity and add it to the db context
                        context.Set<TEntity>().AddOrUpdate((TEntity)entity);
                        context.SaveChanges();
                    }

                    return entity;
                }
                catch (Exception ex)
                {
                    throw new DatabaseAccessException("Error occured while getting saving.", ex);
                }
            }
}

And instead of passing the dbcontext around, we pass around logics.

e.g. we seperate the logic for the database and the access to the database into 2 seperate projects, the business layer then only uses the dbAccess layer.

6 Comments

If you are suggesting something like the Repository pattern, no thanks. We tried that and we decided against it. EF Core is a good enough repository-like entity and by encapsulating it you lose all the flexibility of LINQ.
You certainly do not lose the flexbility of linq, you however move it out of the business layer to an abstraction - ill try an add a sample
Added a simple sample for addOrUpdate, i feel like some more complex stuff we have is company property so i won't share it. But since linq is mostly extensions and so one we have some more complex selector functions where we pass predicates etc and honestly working this way feels super clean.
You lose the flexibility of LINQ-to-SQL if you mean that returning IEnumerable still enables you to use LINQ. Also, Repository vs EF Core is a dead topic for me. There are tens of articles online and I've read many of them and I tried both ways myself. Repository is not worth the effort for me.
It wouldn't even simplify the problem that much in this instance because there are many different ways I'm reading from a table and I would have to construct N repository methods (or go on into reimplementing LINQ-to-SQL with all the complexity that that entails) that would still need to have a central source of truth for row-level authorisation.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.