Как использовать сегментирование базы данных с EF Core и C#"

В настоящее время я занимаюсь преобразованием своего 6-летнего приложения C# в .NET Core v3 и EF Core (а также с использованием Blazor). Большая его часть работает, за исключением части Sharding.
Наше приложение создает новую базу данных для каждого клиента. Мы используем для этого примерно такой код: https://docs.microsoft.com/en-us/azure/sql-database/sql-database-elastic-scale-use-entity-framework-applications-visual-studio
Сейчас я пытаюсь преобразовать его в EF Core, но застреваю на этом этапе:

        // C'tor to deploy schema and migrations to a new shard
        protected internal TenantContext(string connectionString)
            : base(SetInitializerForConnection(connectionString))
        {
        }

        // Only static methods are allowed in calls into base class c'tors
        private static string SetInitializerForConnection(string connnectionString)
        {
            // We want existence checks so that the schema can get deployed
            Database.SetInitializer<TenantContext<T>>(new CreateDatabaseIfNotExists<TenantContext<T>>());
            return connnectionString;
        }

        // C'tor for data dependent routing. This call will open a validated connection routed to the proper
        // shard by the shard map manager. Note that the base class c'tor call will fail for an open connection
        // if migrations need to be done and SQL credentials are used. This is the reason for the 
        // separation of c'tors into the DDR case (this c'tor) and the internal c'tor for new shards.
        public TenantContext(ShardMap shardMap, T shardingKey, string connectionStr)
            : base(CreateDDRConnection(shardMap, shardingKey, connectionStr), true /* contextOwnsConnection */)
        {
        }

        // Only static methods are allowed in calls into base class c'tors
        private static DbConnection CreateDDRConnection(ShardMap shardMap, T shardingKey, string connectionStr)
        {
            // No initialization
            Database.SetInitializer<TenantContext<T>>(null);

            // Ask shard map to broker a validated connection for the given key
            var conn = shardMap.OpenConnectionForKey<T>(shardingKey, connectionStr, ConnectionOptions.Validate);
            return conn;
        }

Приведенный выше код не компилируется, поскольку объект базы данных не существует таким образом в EF Core. Я предполагаю, что могу упростить это, используя TenantContext.Database.EnsureCreated(); где-нибудь. Но я не могу понять, как изменить методы, какие удалить, какие изменить (и как).

Конечно, я искал пример использования сегментирования и EF Core, но не смог его найти. Кто-нибудь здесь делал это раньше в EF Core и хочет поделиться?

Я специально ищу, что добавить в startup.cs и как создать новый сегмент/базу данных при создании нового клиента.


person Paul Meems    schedule 06.05.2020    source источник
comment
Здравствуйте, Пол, вы спрашиваете, как автоматически перенести базу данных с помощью EF Core? Или у вас возникла конкретная ошибка или проблема при создании сегментированного подключения в EF Core?   -  person Code Slinger    schedule 06.05.2020
comment
Привет, Марк, я обновил свой пост. Я не могу скомпилировать код EF, поскольку объект базы данных не существует в EF Core.   -  person Paul Meems    schedule 06.05.2020
comment
Будь осторожен с желаниями. После МЕСЯЦЕВ попыток заставить EfCore работать, я теперь возвращаюсь к Ef classic, который доступен на .NET Core. Слишком много ограничений в сгенерированном SQL, который стал намного хуже в версии 3.1 благодаря тому, что мы даже не ПЫТАЛИСЬ оценить его на клиенте.   -  person TomTom    schedule 06.05.2020
comment
Спасибо @TomTom за предупреждение. Я согласен. Мы начали переход на .NET Core v3 en EF Core v3, ожидая, что v3 означает, что он достаточно зрелый. Но если вы работаете немного нестандартно, это трудно заставить работать. Я также трачу недели на интеграцию с MS Identity, но не смог заставить его работать. Сейчас мы используем Google для входа. Шардинг тоже что-то в этом роде, нет примера кода   -  person Paul Meems    schedule 07.05.2020
comment
@PaulMeems Вы решили проблему? У нас еще есть похожие проблемы.   -  person Milan    schedule 10.02.2021


Ответы (2)


В EF.Core просто разрешите осколок в OnConfiguring. НАПРИМЕР

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    var con = GetTenantConnection(this.tenantName);

    optionsBuilder.UseSqlServer(con,o => o.UseRelationalNulls());

    base.OnConfiguring(optionsBuilder);
}

Обратите внимание, что если у вас есть служба или фабрика, которая возвращает open DbConnections, вам нужно будет их Close()/Dispose() в DbContext.Dispose(). Если вы получаете строку подключения или закрытое соединение, DbContext позаботится о закрытии соединения.

Лучшие практики ASP.NET Core, вероятно, требуют внедрения службы ITenantConfiguration или чего-то подобного в ваш DbContext. Но схема та же. Просто сохраните внедренный экземпляр службы в поле DbContext и используйте его в OnConfiguring.

person David Browne - Microsoft    schedule 06.05.2020
comment
Спасибо @david-browne-microsoft. В процессе я также изучаю .NET Core, поэтому мне сложно понять, как реализовать все части. У вас есть полностью рабочий пример и/или ссылки на документацию? - person Paul Meems; 07.05.2020
comment
Что вы должны делать, если GetTenantConnection() асинхронный? - person Phil; 10.12.2020
comment
@ Дэвид-Браун-Майкрософт. Я не вижу здесь никакой реакции. У вас есть полностью рабочий пример и/или ссылки на документацию? - person Milan; 10.02.2021

В приложении, над которым я работаю, нужный сегмент невозможно обнаружить до времени запроса (например, зная, какой пользователь делает запрос, а затем направляя этого пользователя в свою базу данных). Это означало, что предложенное выше решение OnConfiguring было нежизнеспособным.

Я обошел это, используя IDbContextFactory<TContext> и определив расширение поверх него, которое устанавливает строку подключения на основе того, что вы хотите. Я считаю, что подключение к базе данных создается лениво в EF, и вы можете установить строку подключения до тех пор, пока EF сначала не потребуется фактически подключиться к базе данных.

В моем случае это выглядело примерно так:

var dbContext = _dbContextFactory.CreateDbContext();
var connectionString = $"DataSource={_sqlliteDirectory}/tenant_{tenant.TenantId}.db";

dbContext.Database.SetConnectionString(connectionString);

Недостатком является то, что он нарушает абстракцию базы данных (этот код знает, что моя база данных является локальным экземпляром sqllite). В этом слое моего приложения не было необходимости в абстракции, но это очень легко решить, если это необходимо.

person Michael Fry    schedule 29.07.2021