EF Core HasQueryFilter работает только с первым значением в выражении фильтра.

Я использую метод расширения EF Core HasQueryFilter, который находится внутри метода OnModelCreating.

Я ввожу идентификатор пользователя в DbContext с помощью службы, а затем применяю идентификатор пользователя к фильтру запроса. В первый раз, когда OnModelCreating выполняется, он работает нормально, как и ожидалось. Но когда я меняю пользователя и передаю другой идентификатор пользователя в DbContext, фильтр запроса не затрагивается так очевидно, потому что на этот раз OnModelCreating не вызывается.

Немного предыстории приложения: это основной проект API 2.2, который аутентифицирует пользователей с помощью токена JWT. Я заполняю утверждения пользователя и инициализирую введенную службу аутентификации с помощью JWT, поэтому для каждого вызова API идентификатор пользователя может быть другим, поэтому фильтр запроса должен работать с разными идентификаторами пользователя.

Примеры кодов ниже:

public class SqlContext : DbContext
{
    private readonly IAuthService _authService;

    public SqlContext(DbContextOptions options, IAuthService authService) : base(options)
    {
        _authService = authService;
    }

    public DbSet<Device> Devices { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Device>().HasQueryFilter(p => !p.IsDeleted && p.ManufacturerId == _authService.ManufacturerId);
    }
}

Как инициализируется DbContext.

 services.AddDbContextPool<TContext>(o =>
            o.UseSqlServer(configuration["Settings:SqlServer:DefaultConnection"],
                b =>
                {
                    b.MigrationsAssembly(configuration["Settings:SqlServer:MigrationAssembly"]);
                    b.CommandTimeout(60);
                    b.EnableRetryOnFailure(2);
                })
            .ConfigureWarnings(warnings =>
            {
                warnings.Throw(RelationalEventId.QueryClientEvaluationWarning);
            }))
            .AddTransient<TContext>();

person Mohammed Kamran Azam    schedule 22.02.2019    source источник
comment
Однако это должно работать - как только выражение фильтра использует поле/свойство/метод из контекста базы данных, как в примере кода. Это ваш настоящий код? Я имею в виду, фильтр запроса настроен точно так же, как в сообщении (а не с помощью настраиваемого метода расширения или внешнего класса конфигурации)?   -  person Ivan Stoev    schedule 22.02.2019
comment
Я думаю, что время жизни DbContext важно. modelBuilder вызывается только при запуске, и я не думаю, что QueryFilter срабатывает каждый раз, когда пользователь делает запрос.   -  person Derviş Kayımbaşıoğlu    schedule 22.02.2019
comment
@Simonare Неважно, сколько раз OnModelCreating вызывается, как только контекст БД будет правильно создан (new-ed). Сценарий поддерживается, но требует специальной настройки выражения фильтра.   -  person Ivan Stoev    schedule 22.02.2019
comment
@IvanStoev, да, код именно такой. Таким образом, когда первый пользователь делает вызов, ManufcaturerId устанавливается равным 1, а когда какой-либо другой пользователь делает вызов, тогда ManufacturerId не изменяется и остается равным 1, поскольку OnModelCreating больше не вызывается. Я считаю, что фильтр запроса кэшируется, когда он создается в первый раз.   -  person Mohammed Kamran Azam    schedule 22.02.2019
comment
Как я могу перестроить модель с новым фильтром для каждого запроса пользователя.   -  person Mohammed Kamran Azam    schedule 22.02.2019
comment
Нет необходимости переделывать модель. См. пример фильтра запроса модели и текст после него: Обратите внимание на использование свойства уровня экземпляра DbContext: TenantId. Фильтры уровня модели будут использовать значение из правильного экземпляра контекста (то есть экземпляра контекста, выполняющего запрос). Если код именно такой, и ваш конструктор контекста БД вызывается для каждого запроса, фильтр должен работать.   -  person Ivan Stoev    schedule 22.02.2019
comment
Конструктор DbContext вызывается для каждого запроса. Я также получаю измененные значения, но фильтр все равно не работает. Я отредактировал вопрос, чтобы показать пример того, как я инициализирую DbContext.   -  person Mohammed Kamran Azam    schedule 23.02.2019
comment
@IvanStoev, вы имеете в виду, что DbContext зарегистрирован как Transient, верно?   -  person Derviş Kayımbaşıoğlu    schedule 23.02.2019
comment
Единственное отличие от примера документации (и моих глобальных тестов фильтра запросов, которые доказали, что эта функция работает), я вижу, это использование пула контекста БД. Как указано в документе < /a>, Избегайте использования пула DbContext, если вы поддерживаете собственное состояние (например, закрытые поля) в своем производном классе DbContext, которое не должно использоваться совместно между запросами. Попробуйте заменить services.AddDbContextPool на services.AddDbContext.   -  person Ivan Stoev    schedule 24.02.2019


Ответы (1)


Наконец решил это.

Поскольку фильтр работал, но не обновлялся после создания модели после первого запроса. Причина заключалась в том, что EF кэшировал созданную модель. Итак, мне пришлось реализовать IModelCacheKeyFactory, чтобы захватить разные модели в соответствии с фильтрами.

internal class DynamicModelCacheKeyFactory : IModelCacheKeyFactory
{
    public object Create(DbContext context)
    {
        if (context is SqlContext dynamicContext)
        {
            return (context.GetType(), dynamicContext._roleCategory);
        }
        return context.GetType();
    }
}

И прикрепил его к контексту вот так.

protected override void OnConfiguring(DbContextOptionsBuilder builder)
    {
        base.OnConfiguring(builder);

        builder.ReplaceService<IModelCacheKeyFactory, DynamicModelCacheKeyFactory>();
    }
person Mohammed Kamran Azam    schedule 25.02.2019
comment
Большое спасибо за это решение - я только что понял, что модель кешируется в моей собственной реализации (где фильтры запросов фактически генерируются в коде для каждого конкретного пользователя), поэтому это позволило мне заставить EF использовать кеширование. -пользовательская модель. - person Quango; 13.11.2019