Spécifiez l'édition Azure SQL Server dans EF Core sans interrompre le développement local

9

Entity Framework Core a introduit les méthodes HasServiceTier et HasPerformanceLevel pour modifier l'édition d'un serveur Azure SQL. Vous pouvez les utiliser dans OnModelCreating comme ceci:

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

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

Si vous utilisez Add-Migration Add-Migration, vous obtenez une migration comme celle-ci:

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'");
    }
}

Cela semble fonctionner correctement, mais lorsque j'essaie d'appliquer cette migration à une base de données locale non Azure à des fins de développement, j'obtiens l'erreur suivante:

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 '.'.

Je suppose que les commandes ne sont pas valides pour les bases de données non Azure. La question est donc la suivante: comment empêcher l'exécution de ces commandes sur des bases de données non Azure?

Tim Pohlmann
la source
Comment se déroulent vos migrations? Si dans le code, vous pouvez activer ASPNETCORE_ENVIRONMENT docs.microsoft.com/en-us/aspnet/core/fundamentals/…
Patrick Goode
@PatrickGoode qui ne me permettrait que de désactiver entièrement les migrations pour la base de données locale, non? Je veux que toutes les migrations s'exécutent, sauf celle-ci. Une solution serait de faire dépendre le contenu de la migration d'une variable de configuration. Je me demandais simplement s'il y avait une solution plus élégante.
Tim Pohlmann
1
Plutôt que de gaspiller des primes, vous devriez vraiment le poster sur EF Core Issue Tracker car c'est leur source de bogue / problème . Comme vous pouvez le voir, il existe des blocs conditionnels pour d'autres choses, mais pas pour celui-ci. Bien sûr, vous pouvez remplacer leur classe par custom, mais vous devez copier / coller / modifier la méthode entière.
Ivan Stoev le
1
Je viens de voir que vous l'avez déjà fait - # 20682 . Bonne chance.
Ivan Stoev le
1
@IvanStoev c'est un aperçu intéressant du code source. Merci de l'avoir trouvé.
Tim Pohlmann

Réponses:

2

L'équipe EF Core est maintenant au courant du problème et l'a ajouté à son backlog: https://github.com/dotnet/efcore/issues/20682

Pendant ce temps, la solution de contournement officiellement recommandée ressemble à ceci:

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

Je l'ai modifié pour fonctionner sans connaître le nom de la base de données actuelle:

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
la source
0

Bien sûr, EDITIONet SERVICE_OBJECTIVEne sont pas pris en charge pour les bases de données non Azure SQL.

Vous devez exécuter vos commandes uniquement pour la base de données Azure. Pour les autres types de serveur SQL, vous devez manquer l'exécution de votre code.

Je suggère de détecter SQL Server Edition avant d'exécuter votre code.

À cet effet, vous pouvez ajouter une méthode d'extension:

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);
    }
}

Et à l'intérieur de votre OnModelCreatingméthode, vous pouvez utiliser le code suivant:

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

    if (Database.IsSqlAzure())
    {
        modelBuilder.HasServiceTier("Basic");
        modelBuilder.HasPerformanceLevel("Basic");
    }
}
Alexandre I.
la source
Je doute que ça marche. Le code à l'origine des problèmes se trouve dans la migration et non dans OnModelCreating. J'ai envisagé d'utiliser quelque chose comme ça dans la migration elle-même mais cela semble un peu loufoque, c'est pourquoi j'ai ouvert cette question.
Tim Pohlmann
0

Cela semble très mal, mais cela fonctionne:

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
la source