Home > Software design >  How to create dynamic Linq Expression
How to create dynamic Linq Expression

Time:10-13

Where can I learn about creating a 'dynamic' linq expression? I need to turn this into something dynamic because the currentUser is null when the code is excecuted in the OnModelCreating of Entity Framework. I am trying to create a global query filter.

User currentUser = currentUserService.GetUser();
IPermissionService permissionService = DependencyResolver.Current.GetService<IPermissionService>();

var allowedToSee = Entities.Where(e => permissionService.HasPermission(e, currentUser).View);
modelBuilder.Entity<Audit>().HasQueryFilter(x => allowedToSee.Contains(x.EntityType));

This is the audit class:

public class Audit
    {
        // Information about changes

        public virtual Entity EntityType { get; set; }
    }

Example of other dynamic linq expression:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        { 
            //If the actual entity is an auditable type. 
            if(typeof(Auditable).IsAssignableFrom(entityType.ClrType))
            {
                //This adds (In a reflection type way), a Global Query Filter
                //https://docs.microsoft.com/en-us/ef/core/querying/filters
                //That always excludes deleted items. You can opt out by using dbSet.IgnoreQueryFilters()
                var parameter = Expression.Parameter(entityType.ClrType, "p");
                var deletedCheck = Expression.Lambda(Expression.Equal(Expression.Property(parameter, "DateDeleted"), Expression.Constant(null)), parameter);
                modelBuilder.Entity(entityType.ClrType).HasQueryFilter(deletedCheck);
            }
        }
        
        base.OnModelCreating(modelBuilder);
    }

CodePudding user response:

Assuming that you have the following DbContext and AllowedToSee property is properly implemented, ApplyAuditFilters should apply query filter to all Audit descendants.

public class MyDbContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        ApplyAuditFilters(modelBuilder);

        base.OnModelCreating(modelBuilder);
    }

    // implement this property
    public IEnumerable<Entity> AllowedToSee => throw new NotImplementedException();

    private void ApplyAuditFilters(ModelBuilder modelBuilder)
    {
        var types = modelBuilder.Model.GetEntityTypes()
            .Where(et => typeof(Audit).IsAssignableFrom(et.ClrType));

        var thisExpression = Expression.Constant(this);

        // this.AllowedToSee
        var allowedToSeeExpr = Expression.Property(thisExpression, nameof(AllowedToSee));

        foreach (var et in types)
        {
            var param = Expression.Parameter(et.ClrType, "e");

            // e.EntityType
            var filterPropertyExpr = Expression.Property(param, nameof(Audit.EntityType));

            // this.AllowedToSee.Contains(e.EntityType)
            var filterBody = Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new[] { typeof(Entity) },
                allowedToSeeExpr, filterPropertyExpr);

            // e => this.AllowedToSee.Contains(e.EntityType)
            var queryFilter = Expression.Lambda(filterBody, param);

            modelBuilder.Entity(et.ClrType).HasQueryFilter(queryFilter);
        }
    }

    ... // other members
}

CodePudding user response:

If you need 'dynamic' linq, I recommend using System.Linq.Dynamic.Core.

And here is the documentation for it.

However, I'm not sure that this would be the best solution in your case. But if you want to dynamically generate linq expressions, then this is a good and easy way to do it.

  • Related