【问题标题】:Specify Azure SQL server edition in EF Core without breaking local development在 EF Core 中指定 Azure SQL 服务器版本而不中断本地开发
【发布时间】:2020-04-30 18:25:45
【问题描述】:

Entity Framework Core 引入了 HasServiceTierHasPerformanceLevel 方法来更改 Azure SQL 服务器的版本。您可以像这样在OnModelCreating 中使用它们:

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

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

如果你使用 Add-Migration Add-Migration 你会得到这样的迁移:

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

这似乎工作正常,但是当我尝试将此迁移应用到本地非 Azure DB 以进行开发时,我收到以下错误:

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

我假设这些命令对非 Azure DB 无效。那么问题来了:如何防止这些命令在非 Azure DB 上执行?

【问题讨论】:

  • 您的迁移是如何运行的?如果在代码中,您可以打开 ASPNETCORE_ENVIRONMENT docs.microsoft.com/en-us/aspnet/core/fundamentals/…
  • @PatrickGoode 只能让我完全禁用本地数据库的迁移,对吧?我希望所有迁移都运行,除了这个。一种解决方案是使迁移的内容依赖于配置变量。我只是想知道是否有更优雅的解决方案。
  • 与其浪费赏金,不如将其发布到 EF Core Issue Tracker,因为这是他们的错误/问题 - source。正如你所看到的,还有conditional blocks 用于其他事情,但不是用于这个。当然你可以用自定义替换他们的类,但是你必须复制/粘贴/修改整个方法。
  • 我刚刚看到你已经这样做了 - #20682。祝你好运。
  • @IvanStoev 这是源代码中的一些有趣的见解。感谢您挖掘它。

标签: c# azure entity-framework entity-framework-core


【解决方案1】:

EF Core 团队现已意识到该问题并将其添加到他们的待办事项中: https://github.com/dotnet/efcore/issues/20682

同时官方推荐的解决方法如下:

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

我在不知道当前数据库名称的情况下对其进行了修改:

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

【讨论】:

    【解决方案2】:

    当然,非 Azure SQL 数据库不支持 EDITIONSERVICE_OBJECTIVE

    您只需为 Azure 数据库运行命令。对于其他类型的 SQL 服务器,您需要错过代码的执行。

    我建议在运行代码之前检测SQL Server Edition

    为此,您可以添加扩展方法:

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

    在您的 OnModelCreating 方法中,您可以使用下一个代码:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    
        if (Database.IsSqlAzure())
        {
            modelBuilder.HasServiceTier("Basic");
            modelBuilder.HasPerformanceLevel("Basic");
        }
    }
    

    【讨论】:

    • 我怀疑这行不通。导致问题的代码位于迁移中,而不是在 OnModelCreating 中。我考虑在迁移本身中使用类似的东西,但这似乎有点不稳定,这就是我打开这个问题的原因。
    【解决方案3】:

    这感觉很不对,但确实有效:

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

    【讨论】:

      猜你喜欢
      • 2017-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-11
      • 1970-01-01
      • 2019-05-04
      • 2011-07-25
      • 1970-01-01
      相关资源
      最近更新 更多