Currently (as of EF Core 2.0.0) the dynamic global query filtering is quite limited. It works only if the dynamic part is provided by direct property of the target DbContext
derived class (or one of its base DbContext
derived classes). Exactly as in the Model-level query filters example from the documentation. Exactly that way - no method calls, no nested property accessors - just property of the context. It's sort of explained in the link:
Note the use of a DbContext
instance level property: TenantId
. Model-level filters will use the value from the correct context instance. i.e. the one that is executing the query.
To make it work in your scenario, you have to create a base class like this:
public abstract class TenantDbContext : DbContext
{
protected ITenantProvider TenantProvider;
internal int TenantId => TenantProvider.GetId();
}
derive your context class from it and somehow inject the TenantProvider
instance into it. Then modify the TenantEntityConfigurationBase
class to receive TenantDbContext
:
internal abstract class TenantEntityConfigurationBase<TEntity, TKey> :
EntityConfigurationBase<TEntity, TKey>
where TEntity : TenantEntityBase<TKey>
where TKey : IEquatable<TKey> {
protected readonly TenantDbContext Context;
protected TenantEntityConfigurationBase(
string table,
string schema,
TenantDbContext context) :
base(table, schema) {
Context = context;
}
protected override void ConfigureFilters(
EntityTypeBuilder<TEntity> builder) {
base.ConfigureFilters(builder);
builder.HasQueryFilter(
e => e.TenantId == Context.TenantId);
}
protected override void ConfigureRelationships(
EntityTypeBuilder<TEntity> builder) {
base.ConfigureRelationships(builder);
builder.HasOne(
t => t.Tenant).WithMany().HasForeignKey(
k => k.TenantId);
}
}
and everything will work as expected. And remember, the Context
variable type must be a DbContext
derived class - replacing it with interface won't work.
Update for 2.0.1: As @Smit pointed out in the comments, v2.0.1 removed most of the limitations - now you can use methods and sub properties.
However, it introduced another requirement - the dynamic expression must be rooted at the DbContext
.
This requirement breaks the above solution, since the expression root is TenantEntityConfigurationBase<TEntity, TKey>
class, and it's not so easy to create such expression outside the DbContext
due to lack of compile time support for generating constant expressions.
It could be solved with some low level expression manipulation methods, but the easier in your case would be to move the filter creation in generic instance method of the TenantDbContext
and call it from the entity configuration class.
Here are the modifications:
TenantDbContext class:
internal Expression<Func<TEntity, bool>> CreateFilter<TEntity, TKey>()
where TEntity : TenantEntityBase<TKey>
where TKey : IEquatable<TKey>
{
return e => e.TenantId == TenantId;
}
TenantEntityConfigurationBase<TEntity, TKey> class:
builder.HasQueryFilter(Context.CreateFilter<TEntity, TKey>());