最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - EF Core global query filter based on Nullable property resulting in NullReferenceException - Stack Overflow

programmeradmin1浏览0评论
public partial class AppDbContext(DbContextOptions<AppDbContext> options, ILegalEntityProvider legalEntityProvider) : DbContext(options)
{
    public Guid? LegalEntityId { get; } = legalEntityProvider.LegalEntity;

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);

        // Apply a global query filter to all entities implementing IMultiLegalEntity.
        // This ensures that queries only return records associated with the current LegalEntityId,
        // which is determined from the 'extension_LegalEntityId' claim in the user's identity.
        // The LegalEntityId is retrieved through the LegalEntityProvider, and only records with
        // matching LegalEntityIds will be included in the query results.

        // Global query filters are compiled and cannot be “switched off” on the fly unless you design them to be conditional.
        modelBuilder.SetQueryFilterOnAllEntities<IMultiLegalEntity>(
            x => LegalEntityId.HasValue && x.LegalEntities.Any(le => le.Id == LegalEntityId.Value)
        );
    }
}

I'm using EF Core global query filters to filter records based on LegalEntityId. Essentially, if there is a LegalEntityId in the JWT token, the filter ensures that only records associated with the current LegalEntityId are returned. The LegalEntityId is retrieved from a provider and is applied in the global query filter like above.

The problem is when LegalEntityId is null. Since global query filters in EF Core are compiled once during startup, they don't dynamically adjust to changes in the value of LegalEntityId at runtime. This happens because the filter is evaluated when the model is created and remains static for subsequent queries.

There are similar topics like EF Core global query filter based on nullable property resulting in NullReferenceException, however, I couldn't think of a solution.

It's essentially the same thing as a multi-tenant implementation, however, LegalEntityId can be null. Any idea how to tackle this?

public static class ModelBuilderExtensions
{
    /// <summary>
    /// Applies a global query filter to all entities of the specified type in the <see cref="ModelBuilder"/>.
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity to which the filter will be applied. It must be a class type or interface type.</typeparam>
    /// <param name="builder">The <see cref="ModelBuilder"/> instance to which the global filter will be applied.</param>
    /// <param name="filter">An <see cref="Expression{Func{TEntity, bool}}"/> representing the filter to be applied to the entities.</param>
    /// <returns>The <see cref="ModelBuilder"/> instance with the global query filter applied.</returns>
    /// <remarks>
    /// This method applies the specified filter to all entities that are assignable to the <typeparamref name="TEntity"/> type.
    /// If an entity already has a query filter, the new filter is combined with the existing filter using an AND operation.
    /// </remarks>
    public static ModelBuilder SetQueryFilterOnAllEntities<TEntity>(
        this ModelBuilder builder,
        Expression<Func<TEntity, bool>> filter)
    {
        // Entities that are assignable to the TEntity generic
        var entities = builder.Model.GetEntityTypes()
            .Where(entity => typeof(TEntity).IsAssignableFrom(entity.ClrType))
            .ToList();

        foreach (var entity in entities)
        {
            // Creating a ParameterExpression with the actual type of the current entity
            var entityParam = Expression.Parameter(entity.ClrType, "entity");

            // Replacing the parameter with the actual entity parameter / (e.g => (entityParameter => ....))
            var filterBody = ReplacingExpressionVisitor.Replace(filter.Parameters[0], entityParam, filter.Body);

            // Getting the current query filters of the actual entity
            var existingFilter = entity.GetQueryFilter();

            if (existingFilter is not null)
            {
                // Other filter already present, combine them
                filterBody = ReplacingExpressionVisitor.Replace(entityParam, existingFilter.Parameters[0], filterBody);
                filterBody = Expression.AndAlso(existingFilter.Body, filterBody);

                // Create a new lambda expression for the combined filter
                existingFilter = Expression.Lambda(filterBody, existingFilter.Parameters);
            }
            else
            {
                // Create a new lambda expression for the current filter
                existingFilter = Expression.Lambda(filterBody, entityParam);
            }

            // Setting the query filter to the entity
            entity.SetQueryFilter(existingFilter);
        }

        return builder;
    }
}
发布评论

评论列表(0)

  1. 暂无评论