Tentukan edisi Azure SQL server di EF Core tanpa merusak pengembangan lokal

9

Entity Framework Core memperkenalkan metode HasServiceTier dan HasPerformanceLevel untuk mengubah edisi server Azure SQL. Anda dapat menggunakannya di OnModelCreating seperti ini:

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

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

Jika Anda menggunakan Add-Migration Add-Migration Anda mendapatkan migrasi seperti ini:

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

Ini tampaknya berfungsi dengan baik tetapi ketika saya mencoba menerapkan migrasi ini ke DB non-Azure lokal untuk tujuan pengembangan, saya mendapatkan kesalahan berikut:

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

Saya menganggap perintah tidak valid untuk DB non-Azure. Jadi pertanyaannya adalah: Bagaimana saya bisa mencegah perintah ini dieksekusi pada DB non-Azure?

Tim Pohlmann
sumber
Bagaimana migrasi Anda berjalan? Jika dalam kode, Anda dapat mengaktifkan ASPNETCORE_ENVIRONMENT docs.microsoft.com/en-us/aspnet/core/fundamentals/…
Patrick Goode
@ PatrickGoode yang hanya akan memungkinkan saya untuk menonaktifkan migrasi untuk db lokal sepenuhnya, kan? Saya ingin semua migrasi berjalan kecuali yang ini. Salah satu solusinya adalah membuat konten migrasi bergantung pada variabel-konfigurasi. Saya hanya ingin tahu apakah ada solusi yang lebih elegan.
Tim Pohlmann
1
Daripada membuang-buang karunia, Anda harus benar-benar posting ke EF Inti Issue Tracker karena bug mereka / masalah - sumber . Seperti yang Anda lihat, ada blok bersyarat untuk hal-hal lain, tetapi tidak untuk yang ini. Tentu saja Anda dapat mengganti kelas mereka dengan kustom, tetapi Anda harus menyalin / menempel / memodifikasi seluruh metode.
Ivan Stoev
1
Saya baru saja melihat bahwa Anda sudah melakukan itu - # 20682 . Semoga berhasil.
Ivan Stoev
1
@IvanStoev, itulah beberapa wawasan menarik dalam kode sumber. Terima kasih telah menggali itu.
Tim Pohlmann

Jawaban:

2

Tim EF Core sekarang menyadari masalah ini dan menambahkannya ke dalam simpanan mereka: https://github.com/dotnet/efcore/issues/20682

Sementara itu solusi yang direkomendasikan secara resmi terlihat seperti ini:

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

Saya memodifikasinya agar berfungsi tanpa mengetahui nama basis data saat ini:

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
sumber
0

Tentu saja EDITIONdan SERVICE_OBJECTIVEtidak didukung untuk Database SQL non Azure.

Anda hanya perlu menjalankan perintah untuk basis data Azure. Untuk jenis lain dari server SQL Anda harus kehilangan eksekusi kode Anda.

Saya sarankan untuk mendeteksi SQL Server Edition sebelum menjalankan kode Anda.

Untuk tujuan ini, Anda dapat menambahkan metode ekstensi:

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

Dan di dalam OnModelCreatingmetode Anda, Anda dapat menggunakan kode berikutnya:

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

    if (Database.IsSqlAzure())
    {
        modelBuilder.HasServiceTier("Basic");
        modelBuilder.HasPerformanceLevel("Basic");
    }
}
Alexander I.
sumber
Saya ragu ini akan berhasil. Kode yang menyebabkan masalah terletak di migrasi bukan di OnModelCreating. Saya mempertimbangkan untuk menggunakan sesuatu seperti itu dalam migrasi itu sendiri tetapi itu tampaknya agak miring, itulah sebabnya saya membuka pertanyaan ini.
Tim Pohlmann
0

Ini terasa sangat salah tetapi berhasil:

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
sumber