Это не имеет ничего общего с кэшированием и контекстными экземплярами. Это проблема, связанная с деревьями выражений - смотрите мое обновление.

даю мультитенантное приложение и сталкиваюсь с трудностями, связанными с кэшированием идентификатора клиента в запросах. Единственная вещь, которая, кажется, помогает - постоянно перестраивать приложение, когда я вхожу и покидаю арендаторов.

Я думал, что это может иметь какое-то отношение кIHttpContextAccessor экземпляр является одиночным, но он не может быть ограничен областью действия, и когда я вхожу и выхожу без перестройки, я вижу изменение имени арендатора в верхней части страницы, так что это не проблема.

Единственное, о чем я могу думать, это то, что EF Core выполняет какое-то кэширование запросов. Я не уверен, почему он будет считать, что это экземпляр с заданной областью, и он должен перестраиваться при каждом запросе, если я не ошибаюсь, что, вероятно, и есть. Я надеялся, что он будет вести себя как экземпляр с заданной областью, поэтому я мог бы просто ввести идентификатор клиента во время построения модели для каждого экземпляра.

Я был бы очень признателен, если бы кто-то указал мне правильное направление. Вот мой текущий код:

TenantProvider.cs

public sealed class TenantProvider :
    ITenantProvider {
    private readonly IHttpContextAccessor _accessor;

    public TenantProvider(
        IHttpContextAccessor accessor) {
        _accessor = accessor;
    }

    public int GetId() {
        return _accessor.HttpContext.User.GetTenantId();
    }
}

... который вводится вTenantEntityConfigurationBase.cs где я использую его для настройки глобального фильтра запросов.

internal abstract class TenantEntityConfigurationBase<TEntity, TKey> :
    EntityConfigurationBase<TEntity, TKey>
    where TEntity : TenantEntityBase<TKey>
    where TKey : IEquatable<TKey> {
    protected readonly ITenantProvider TenantProvider;

    protected TenantEntityConfigurationBase(
        string table,
        string schema,
        ITenantProvider tenantProvider) :
        base(table, schema) {
        TenantProvider = tenantProvider;
    }

    protected override void ConfigureFilters(
        EntityTypeBuilder<TEntity> builder) {
        base.ConfigureFilters(builder);

        builder.HasQueryFilter(
            e => e.TenantId == TenantProvider.GetId());
    }

    protected override void ConfigureRelationships(
        EntityTypeBuilder<TEntity> builder) {
        base.ConfigureRelationships(builder);

        builder.HasOne(
            t => t.Tenant).WithMany().HasForeignKey(
            k => k.TenantId);
    }
}

... который затем наследуется всеми другими конфигурациями объекта-арендатора. К сожалению, это не работает так, как я планировал.

Я проверил, что идентификатор арендатора, возвращаемый принципалом пользователя, изменяется в зависимости от того, какой пользователь арендатора вошел в систему, поэтому проблема не в этом. Заранее благодарю за любую помощь!

Обновить

Для решения при использовании EF Core 2.0.1+ посмотрите на непринятый от меня ответ.

Обновление 2

Также посмотрите на обновление Ивана для 2.0.1+, оно прокси в выражении фильтра из DbContext, которое восстанавливает возможность определить его один раз в базовом классе конфигурации. Оба решения имеют свои плюсы и минусы. Я снова выбрал Ивана, потому что я просто хочу максимально использовать свои базовые конфигурации.

 CalC13 нояб. 2017 г., 16:48
Почему не могуIHttpContextAccessor быть ограниченным / переходным? Возможно, стоит показать соответствующие элементы вашей конфигурации внедрения зависимостей.
 Gup3rSuR4c13 нояб. 2017 г., 23:58
@TsengTenantProvider и DbContext - оба экземпляра области действия. Ответ Ивана решил это, потому что именно так работал EF.
 Tseng13 нояб. 2017 г., 16:54
ЯвляетсяTenantProvider объем или синглтон? А ваши экземпляры DbContext тоже синглтоны? Слишком мне кажется, что что-то переживает запрос, часто намек на пожизненные проблемы с вашим IoC
 Tseng13 нояб. 2017 г., 16:50
@CalC: потому что это синглтон (в основном, своего рода завод) и называть егоHttpContext свойство возвращает http-контекст текущего запроса

Ответы на вопрос(2)

Ответ для 2.0.1+

Итак, в тот день, когда я начал работать, был выпущен EF Core 2.0.1. Как только я обновил, это решение рухнуло. После очень долгой нитиВотОказалось, что это действительно была случайность, что она работала в 2.0.0.

Официально для 2.0.1 и выше любые фильтры запросов, которые зависят от внешнего значения, такого как идентификатор клиента в моем случае, должны быть определены вOnModelCreating метода также должен ссылаться насвойство наDbContext, Причина в том, что при первом запуске приложения или первом вызове EF всеEntityTypeConfiguration классы обрабатываются и их результаты кэшируются независимо от того, сколько разDbContext пример.

Вот почему определение фильтров запросов вOnModelCreating Метод работает, потому что это свежий экземпляр, и фильтр живет и умирает вместе с ним.

public class MyDbContext : DbContext {
    private readonly ITenantService _tenantService;

    private int TenantId => TenantService.GetId();

    public DbSet<User> Users { get; set; }

    public MyDbContext(
        DbContextOptions options,
        ITenantService tenantService) {
        _tenantService = tenantService;
    }

    protected override void OnModelCreating(
        ModelBuilder modelBuilder) {
        modelBuilder.Entity<User>().HasQueryFilter(
            u => u.TenantId == TenantId);
    }
}
 Ivan Stoev16 нояб. 2017 г., 14:09
Это не имеет ничего общего с кэшированием и контекстными экземплярами. Это проблема, связанная с деревьями выражений - смотрите мое обновление.
Решение Вопроса

льтрация запросов довольно ограничена. Оно работаеттолько если динамическая часть обеспечиваетсяпрямая собственность целиDbContext производный класс (или один из его базовыхDbContext производные классы). Точно так же, как вФильтры запросов на уровне модели пример из документации. Именно так - без вызовов методов, без вложенных методов доступа к свойствам - только свойство контекста. Это как-то объясняется в ссылке:

Обратите внимание на использованиеDbContext свойство уровня экземпляра:TenantId, Фильтры уровня модели будут использовать значение из правильного экземпляра контекста. то есть тот, который выполняет запрос.

Чтобы это работало в вашем сценарии, вы должны создать базовый класс, подобный этому:

public abstract class TenantDbContext : DbContext
{
    protected ITenantProvider TenantProvider;
    internal int TenantId => TenantProvider.GetId();
}

вывести из него свой контекстный класс и каким-то образом внедритьTenantProvider экземпляр в это. Затем изменитеTenantEntityConfigurationBase класс, чтобы получить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);
    }
}

и все будет работать как положено. И помните,Context тип переменной должен бытьDbContext полученныйкласс - заменить его наинтерфейс не сработает

Обновление для 2.0.1: Как отметил @Smit в комментариях, v2.0.1 снял большинство ограничений - теперь вы можете использовать методы и вспомогательные свойства.

Однако введено еще одно требование - динамическое выражениедолжен бытьукоренившийся наDbContext.

Это требование нарушает вышеприведенное решение, так как выражение rootTenantEntityConfigurationBase<TEntity, TKey> класс, и это не так просто создать такое выражение внеDbContext из-за отсутствия поддержки времени компиляции для генерации константных выражений.

Это можно решить с помощью некоторых низкоуровневых методов манипуляции выражениями, но в вашем случае проще будет перенести создание фильтра вродовой экземпляр методTenantDbContext и вызвать его из класса конфигурации объекта.

Вот модификации:

Класс TenantDbContext:

internal Expression<Func<TEntity, bool>> CreateFilter<TEntity, TKey>()
    where TEntity : TenantEntityBase<TKey>
    where TKey : IEquatable<TKey>
{
    return e => e.TenantId == TenantId;
}

Класс TenantEntityConfigurationBase <TEntity, TKey>:

builder.HasQueryFilter(Context.CreateFilter<TEntity, TKey>());
 Smit15 нояб. 2017 г., 21:53
Это может помочь немногоgithub.com/aspnet/EntityFrameworkCore/releases/tag/2.0.1 Что касается ограничений, некоторые из них упоминаются в блоге, связанном с выпуском. Но это, как правило, основные ограничения, которые стоили того, что заставляло нас откладывать реализацию. Хотя это также является серьезным ограничением, оно оказалось непреднамеренным. Таким образом, пока люди не начали бить это, у нас не было идеи, что это ограничение. Но как только мы узнаем, мы улучшаем это.
 Smit15 нояб. 2017 г., 21:54
Кроме того, как правило, ограничения не должны быть удалены в небольших версиях. (его особенность, не должна быть частью патча). Мы случайно оказались в процессе исправления еще одной ошибки.
 Ivan Stoev08 февр. 2018 г., 09:44
@MU 1. К сожалению, в этой области немногое, так что это основано на моих личных экспериментах. Я показываю различные подходы настройки фильтра запросов для нескольких объектов здесьEF Core: мягкое удаление со свойствами тени и фильтрами запросов, 2. Я не имею ни малейшего понятия в этом направлении, вероятно, те же методы, которые используются в EF6, например, переопределениеSaveChanges и изученияChangeTracker.Entries()
 Ivan Stoev08 февр. 2018 г., 09:06
@MU Создайте универсальный метод экземпляра внутри вашего контекста БД:private void SetTenantFilter<TEntity>(ModelBuilder modelBuilder) where TEntity : class, ITenantEntity { modelBuilder.Entity<TEntity>().HasQueryFilter(e => e.TenantId == this._TenantId); } Затем вызовите его, используя отражение внутри цикла:foreach (var type in modelBuilder.Model.GetEntityTypes()) { if (typeof(ITenantEntity).IsAssignableFrom(type.ClrType)) GetType().GetMethod("SetTenantFilter", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(type.ClrType).Invoke(this, new[] { modelBuilder }); }
 Ivan Stoev08 февр. 2018 г., 08:26
@MU 1. Это первый подход кода 2. EntityConfigurationBase - это некоторый класс OP. Вы можете просто проигнорировать это. Также нет необходимости использовать отдельные классы для конфигурации - вы можете просто поместить весь код конфигурации вOnModelCreating, Для настройки фильтра для каждогоITenantEntity Вы можете использовать такой же цикл, как в другом вопросе. Но фильтр должен использоватьTenantId свойство контекста, иначе оно не будет динамическим. Как пример вdocs.microsoft.com/en-us/ef/core/what-is-new/..., но для каждой организации, реализующейITenantEntity.

Ваш ответ на вопрос