Especifique a edição do servidor SQL do Azure no EF Core sem interromper o desenvolvimento local

9

O Entity Framework Core apresentou os métodos HasServiceTier e HasPerformanceLevel para alterar a edição de um servidor SQL do Azure. Você pode usá-los no OnModelCreating assim:

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

    modelBuilder.HasServiceTier("Basic");
    modelBuilder.HasPerformanceLevel("Basic");
}

Se você usa Add-Migration Add-Migration, você obtém uma migração como esta:

public partial class ChangedDatabaseServiceTierToBasic : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.AlterDatabase()
            .Annotation("SqlServer:EditionOptions", "EDITION = 'Basic', SERVICE_OBJECTIVE = 'Basic'");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.AlterDatabase()
            .OldAnnotation("SqlServer:EditionOptions", "EDITION = 'Basic', SERVICE_OBJECTIVE = 'Basic'");
    }
}

Isso parece funcionar bem, mas quando tento aplicar essa migração a um banco de dados local que não seja do Azure para fins de desenvolvimento, recebo o seguinte erro:

Microsoft.EntityFrameworkCore.Migrations[20402]
      Applying migration '20200413102908_ChangedDatabaseServiceTierToBasic'.
Applying migration '20200413102908_ChangedDatabaseServiceTierToBasic'.
fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
      Failed executing DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      BEGIN
      DECLARE @db_name NVARCHAR(MAX) = DB_NAME();
      EXEC(N'ALTER DATABASE ' + @db_name + ' MODIFY ( 
      EDITION = ''Basic'', SERVICE_OBJECTIVE = ''Basic'' );');
      END
Failed executing DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
BEGIN
DECLARE @db_name NVARCHAR(MAX) = DB_NAME();
EXEC(N'ALTER DATABASE ' + @db_name + ' MODIFY ( 
EDITION = ''Basic'', SERVICE_OBJECTIVE = ''Basic'' );');
END
Microsoft.Data.SqlClient.SqlException (0x80131904): Incorrect syntax near '.'.
   at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at Microsoft.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean isAsync, Int32 timeout, Boolean asyncWrite)
   at Microsoft.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, Boolean sendToPipe, Int32 timeout, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry, String methodName)
   at Microsoft.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteNonQuery(RelationalCommandParameterObject parameterObject)
   at Microsoft.EntityFrameworkCore.Migrations.MigrationCommand.ExecuteNonQuery(IRelationalConnection connection, IReadOnlyDictionary`2 parameterValues)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationCommandExecutor.ExecuteNonQuery(IEnumerable`1 migrationCommands, IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(String targetMigration)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.UpdateDatabase(String targetMigration, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.UpdateDatabaseImpl(String targetMigration, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.UpdateDatabase.<>c__DisplayClass0_0.<.ctor>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
ClientConnectionId:d9f92b81-9916-48ee-9686-6d0f567ab86f
Error Number:102,State:1,Class:15
Incorrect syntax near '.'.

Presumo que os comandos não sejam válidos para bancos de dados que não sejam do Azure. Portanto, a pergunta é: como impedir que esses comandos sejam executados em bancos de dados que não sejam do Azure?

Tim Pohlmann
fonte
Como são executadas suas migrações? Se estiver no código, você pode ativar o ASPNETCORE_ENVIRONMENT docs.microsoft.com/en-us/aspnet/core/fundamentals/…
Patrick Goode
@PatrickGoode que só me permitia desativar as migrações para o banco de dados local inteiramente, certo? Quero que todas as migrações sejam executadas, exceto esta. Uma solução seria tornar o conteúdo da migração dependente de uma variável de configuração. Eu apenas me perguntei se existe uma solução mais elegante.
Tim Pohlmann
11
Em vez de desperdiçar recompensas, você realmente deve publicá-lo no EF Core Issue Tracker, porque é a fonte do bug / problema . Como você pode ver, existem blocos condicionais para outras coisas, mas não para esta. É claro que você pode substituir a classe deles por personalizada, mas é necessário copiar / colar / modificar todo o método.
Ivan Stoev 25/04
11
Acabei de ver que você já fez isso - # 20682 . Boa sorte.
Ivan Stoev
11
@IvanStoev, essa é uma visão interessante do código fonte. Obrigado por desenterrá-lo.
Tim Pohlmann

Respostas:

2

A equipe do EF Core agora está ciente do problema e o adicionou ao backlog: https://github.com/dotnet/efcore/issues/20682

Enquanto isso, a solução alternativa oficialmente recomendada é a seguinte:

migrationBuilder.Sql(@"IF SERVERPROPERTY('EngineEdition') = 5
EXEC(N'ALTER DATABASE [ThreeOne.SomeDbContext] MODIFY (EDITION = ''Basic'',  SERVICE_OBJECTIVE = ''Basic'' );');
");

Modifiquei-o para funcionar sem saber o nome do banco de dados atual:

migrationBuilder.Sql
(
@"declare @dbname varchar(100)
set @dbname=quotename(db_name())
IF SERVERPROPERTY('EngineEdition') = 5
EXEC(N'ALTER DATABASE '+@dbname+' MODIFY (EDITION = ''Basic'', SERVICE_OBJECTIVE = ''Basic'' );');"
);
Tim Pohlmann
fonte
0

Obviamente, EDITIONe SERVICE_OBJECTIVEnão há suporte para o Banco de Dados SQL do Azure.

Você precisa executar seus comandos apenas no banco de dados do Azure. Para outros tipos de servidor SQL, você precisa perder a execução do seu código.

Sugiro detectar o SQL Server Edition antes de executar seu código.

Para esse fim, você pode adicionar o método de extensão:

public static class DatabaseFacadeExtensions
{
    public static bool IsSqlAzure(this DatabaseFacade database)
    {
        var parameter = new SqlParameter("edition", SqlDbType.NVarChar)
        {
            Size = 128,
            Direction = ParameterDirection.Output
        };

        database.ExecuteSqlCommand("SELECT @edition = CAST(SERVERPROPERTY('Edition') AS NVARCHAR)", parameter);

        var edition = parameter.Value.ToString();

        return edition.Equals("SQL Azure", StringComparison.OrdinalIgnoreCase);
    }
}

E dentro do seu OnModelCreatingmétodo, você pode usar o próximo código:

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

    if (Database.IsSqlAzure())
    {
        modelBuilder.HasServiceTier("Basic");
        modelBuilder.HasPerformanceLevel("Basic");
    }
}
Alexander I.
fonte
Duvido que isso vá funcionar. O código que causa problemas está localizado na migração e não no OnModelCreating. Pensei em usar algo assim na própria migração, mas isso parece um pouco instável, e foi por isso que abri esta pergunta.
Tim Pohlmann
0

Isso parece muito errado, mas funciona:

public partial class ChangedDatabaseServiceTierToBasic : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        if (IsHostedInAzure())
        {
            migrationBuilder.AlterDatabase()
                .Annotation("SqlServer:EditionOptions", "EDITION = 'Basic', SERVICE_OBJECTIVE = 'Basic'");
        }
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        if (IsHostedInAzure())
        {
            migrationBuilder.AlterDatabase()
                .OldAnnotation("SqlServer:EditionOptions", "EDITION = 'Basic', SERVICE_OBJECTIVE = 'Basic'");
        }
    }

    private static bool IsHostedInAzure()
    {
        var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
        var config = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env}.json", optional: true, reloadOnChange: true)
            .Build();

        var isHostedInAzureConfig = config["DatabaseSettings:IsHostedInAzure"];
        var setEdition = bool.TryParse(isHostedInAzureConfig, out var isHostedInAzure) && isHostedInAzure;
        return setEdition;
    }
}
Tim Pohlmann
fonte