Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Global Filter based on Interface

Is it possible to write the below in one line?

//Create a Global Filter for the TenantId property.
modelBuilder.Entity<Item>().HasQueryFilter(b => EF.Property<int>(b, "TenantId") == this._appUserProvider.CurrentTenantId);
modelBuilder.Entity<Invite>().HasQueryFilter(b => EF.Property<int>(b, "TenantId") == this._appUserProvider.CurrentTenantId);

I have tried an Interface for Item and Invite, but EF threw an error saying it must be a reference type.

I also tried a base class, but I don't want to change the base tables which seemed to be required to make that work.

Any other options?

like image 851
Greg Gum Avatar asked May 14 '18 12:05

Greg Gum


People also ask

What is global query filter?

Global query filters are LINQ query predicates applied to Entity Types in the metadata model (usually in OnModelCreating ). A query predicate is a boolean expression typically passed to the LINQ Where query operator. EF Core applies such filters automatically to any LINQ queries involving those Entity Types.

What are called global filters?

Global filters are used to control the display of one or more reports in a single portal page or in a dashboard. For example, a global filter can be on a report that contains only a prompt or prompt controls. This allows for a single selection to drive a number of reports at once.

How do I filter data in Entity Framework?

To filter data, use linq. You can not use Filter property of BindingSource when the underlying list is BindingList<T> ; Only underlying lists that implement the IBindingListView interface support filtering. To remove filter, just set the data source of your binding source to the local storage of your entities again.


1 Answers

Let say your entities implement the following interface:

public interface ITenantEntity
{
    int TenantId { get; set; }
}

The most important thing when using common code to configure multiple entities via fluent API is to use the real entity type when calling modelBuilder.Entity<TEntity>(). Using a base class will introduce EF inheritance, and interface will simply generate exception.

The easiest solution IMO is to put the common code in a generic method and call it via reflection.

Start by creating a constrained generic instance method in your derived DbContext class containing the desired fluent configuration:

void ConfigureTenantFilter<TEntity>(ModelBuilder modelBuilder)
    where TEntity : class, ITenantEntity
{
    modelBuilder.Entity<TEntity>()
        .HasQueryFilter(e => e.TenantId == this._appUserProvider.CurrentTenantId);
}

Then use the following snippet at the end of your OnModelCreating override (after all entity types are discovered):

var configureTenantMethod = GetType().GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ConfigureTenantFilter));
var args = new object[] { modelBuilder };
var tenantEntityTypes = modelBuilder.Model.GetEntityTypes()
    .Where(t => typeof(ITenantEntity).IsAssignableFrom(t.ClrType));
foreach (var entityType in tenantEntityTypes)
    configureTenantMethod.MakeGenericMethod(entityType.ClrType).Invoke(this, args);
like image 86
Ivan Stoev Avatar answered Oct 03 '22 18:10

Ivan Stoev