【问题标题】:Set database collation in Entity Framework Code-First Initializer在 Entity Framework Code-First Initializer 中设置数据库排序规则
【发布时间】:2012-08-16 19:15:51
【问题描述】:

我想在 Entity Framework Code First 创建数据库时为其设置默认排序规则。

我尝试了以下方法:

public class TestInitializer<T> : DropCreateDatabaseAlways<T> where T: DbContext
{
    protected override void Seed(T context)
    {
        context.Database.ExecuteSqlCommand("ALTER DATABASE [Test] SET SINGLE_USER WITH ROLLBACK IMMEDIATE");
        context.Database.ExecuteSqlCommand("ALTER DATABASE [Test] COLLATE Latin1_General_CI_AS");
        context.Database.ExecuteSqlCommand("ALTER DATABASE [Test] SET MULTI_USER");
    }
}

当 SQL Server已经设置为相同的默认排序规则 Latin1_General_CI_AS 时,这似乎运行正常。

但是如果我指定一个 不同的 排序规则,比如 SQL_Latin1_General_CP1_CI_AS 这将失败并出现错误,

System.Data.SqlClient.SqlException: Resetting the connection results in a different 
state than the initial login. The login fails.

谁能告诉我如何设置排序规则?

【问题讨论】:

  • 你们都解决了这个问题吗?我也有同样的问题。
  • 还没有答案。作为一种解决方法,我使用查询手动设置数据库排序规则。
  • 但是,此时所有列都已由 EF 创建......所以有点太晚了。
  • 命令拦截器为我们解决了问题。我在下面的答案中提供了源代码。

标签: sql-server entity-framework-4.1 ef-code-first


【解决方案1】:

使用命令拦截器的解决方案

这绝对是可能的,尽管它有点小技巧。您可以使用命令拦截器更改 CREATE DATABASE 命令。 Il 将拦截发送到数据库的所有命令,根据正则表达式识别数据库创建命令,并使用您的排序规则更改命令文本。

创建数据库之前

DbInterception.Add(new CreateDatabaseCollationInterceptor("SQL_Romanian_Cp1250_CI_AS_KI_WI"));

拦截器

public class CreateDatabaseCollationInterceptor : IDbCommandInterceptor
{
    private readonly string _collation;

    public CreateDatabaseCollationInterceptor(string collation)
    {
        _collation = collation;
    }

    public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { }
    public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
        // Works for SQL Server
        if (Regex.IsMatch(command.CommandText, @"^create database \[.*]$"))
        {
            command.CommandText += " COLLATE " + _collation;
        }
    }
    public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { }
    public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { }
    public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { }
    public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { }
}

备注

由于从一开始就使用正确的排序规则创建数据库,所有列都会自动继承该排序规则,之后您就不必更改它们。

请注意,这将影响以后在应用程序域内创建的任何数据库。因此,您可能希望在创建数据库后删除拦截器。

【讨论】:

  • 在哪里添加“DbInterception.Add”?
  • 我把它放到Configuration 类的构造方法中(Seed 方法所在的地方),它工作得很好。此答案应标记为正确。
【解决方案2】:

我能够通过自定义迁移 (EF6) 更改排序规则。我启用了自动迁移。您需要先删除您的数据库。

  1. 通过在包管理器控制台中键入Add-Migration [YourCustomMigration] 创建迁移代码。 (Code First Migrations)
  2. 第一步应该使用 Up() 覆盖中的当前模型创建代码创建迁移类。在表创建代码之前添加您的ALTER DATABASE 代码,以便使用您想要的数据库排序规则创建它们。另外,请注意suppressTransaction 标志:

public override void Up() { Sql("ALTER DATABASE [YourDB] COLLATE [YourCollation]", suppressTransaction: true); [...Your DB Objects Creation codes here...] }

从那时起发出的每个update-database 命令都会创建一个新的迁移类。所有迁移代码按顺序执行。

【讨论】:

  • 我使用这种技术来改变数据库文件大小和自动增长。
  • 我收到这样的错误:重置连接会导致与初始登录不同的状态。登录失败。用户“调试”登录失败。无法继续执行,因为会话处于终止状态。当前命令发生严重错误。结果(如果有)应丢弃。
【解决方案3】:

我使用 EFCore 的解决方案是从 SqlServerMigrationsSqlGenerator 派生并覆盖 Generate(SqlServerCreateDatabaseOperation, IModel, MigrationCommandListBuilder)

    internal class CustomSqlServerMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator
    {
        internal const string DatabaseCollationName = "SQL_Latin1_General_CP1_CI_AI";

        public CustomSqlServerMigrationsSqlGenerator(
            MigrationsSqlGeneratorDependencies dependencies,
            IMigrationsAnnotationProvider migrationsAnnotations)
        : base(dependencies, migrationsAnnotations)
        {
        }

        protected override void Generate(
            SqlServerCreateDatabaseOperation operation,
            IModel model,
            MigrationCommandListBuilder builder)
        {
            base.Generate(operation, model, builder);

            if (DatabaseCollationName != null)
            {
                builder
                    .Append("ALTER DATABASE ")
                    .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name))
                    .Append(" COLLATE ")
                    .Append(DatabaseCollationName)
                    .AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator)
                    .EndCommand(suppressTransaction: true);
            }
        }
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        optionsBuilder.ReplaceService<IMigrationsSqlGenerator, CustomSqlServerMigrationsSqlGenerator>();
    }

然后通过替换 IMigrationsSqlGenerator 服务在 DbContext 中使用它

public class MyDbContext : DbContext
{
    //...

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        optionsBuilder.ReplaceService<IMigrationsSqlGenerator, CustomSqlServerMigrationsSqlGenerator>();
    }

    //...
}

【讨论】:

    【解决方案4】:

    前段时间我也遇到过同样的问题。可能的解决方案:

    1. 似乎 EF 使用服务器默认值创建数据库 排序规则所以你可以做的一件事就是改变它。
    2. 您不能在Seed() 方法中更改数据库排序规则 但您可以更改表的各个列的排序规则 (注意:没有表排序规则之类的东西,它确实与 表中的列)。您将不得不更改每列的排序规则 分开。
    3. 如果您使用迁移,您可以更改表列 Up() 方法中的排序规则。

    当您使用Seed() 方法时,我建议在Seed() 方法中使用以下内容(根据需要进行修改):

    context.Database.ExecuteSqlCommand(
    @"ALTER TABLE MyTable ALTER COLUMN MyColumn NVARCHAR(max) COLLATE MyCollation NOT NULL");
    

    希望对您有所帮助。

    【讨论】:

    • 这项工作量很大,因为您必须从 sys.columns 表中检索有关表、列、列类型、排序规则、稀疏、空值等信息并创建更改列相应的陈述。这当然是一个hackaround,但没有解决原始问题。
    【解决方案5】:

    我想解释一下为什么你不应该为此使用种子方法。如果在添加任何列后更改数据库排序规则,collation conflicts 的风险很大,如下所示

    无法解决之间的排序规则冲突 “SQL_Latin1_General_CP1_CI_AS”和“Latin1_General_100_CI_AS”中的 等于运算。

    这是因为如果您使用 ALTER DATABASE [YourDb] COLLATE [YourCollation] 更改数据库,您只会更改数据库排序规则,而不是以前创建的列。

    T-SQL 中的示例:

    DECLARE @DBName nvarchar(50), @SQLString nvarchar(200)
    SET @DBName = db_name();
    SET @SQLString = 'ALTER DATABASE [' + @DBName + '] COLLATE Latin1_General_100_CI_AS'
    EXEC(@SQLString)
    
    /* Find Collation of SQL Server Database */
    SELECT DATABASEPROPERTYEX(@DBName, 'Collation')
    /* Find Collation of SQL Server Database Table Column */
    
    SELECT name, collation_name
    FROM sys.columns
    WHERE OBJECT_ID IN (SELECT OBJECT_ID
    FROM sys.objects
    WHERE type = 'U'
    AND name = 'AspNetUsers')
    AND name = 'FirstName'
    

    因此,您需要在添加任何列或单独更改每一列之前更改数据库排序规则。可能的解决方案:

    1. @MathieuRenda https://stackoverflow.com/a/42576705/3850405

    按照文档中的建议,我会将DbInterception.Add 放在派生自DbConfiguration 的类中或Application_Start 中的Global.asax 中。注:Wherever you put this code, be careful not to execute DbInterception.Add for the same interceptor more than once, or you'll get additional interceptor instances.

    public class ApplicationDbConfiguration: DbConfiguration
    {
        public ApplicationDbConfiguration()
        {
            DbInterception.Add(new CreateDatabaseCollationInterceptor("Latin1_General_100_CI_AS"));
        }
    }
    

    我也不会从接口继承,而是使用 DbCommandInterceptor 的实现,就像 Microsoft 在他们的示例中所做的那样。

    using System.Data.Common;
    using System.Data.Entity.Infrastructure.Interception;
    using System.Text.RegularExpressions;
    
    namespace Application.Repositories.EntityFramework
    {
        public class CreateDatabaseCollationInterceptor : DbCommandInterceptor
        {
            private readonly string _collation;
    
            public CreateDatabaseCollationInterceptor(string collation)
            {
                _collation = collation;
            }
    
            public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
            {
                // Works for SQL Server
                if (Regex.IsMatch(command.CommandText, @"^create database \[.*]$"))
                {
                    command.CommandText += " COLLATE " + _collation;
                }
            }
        }
    }
    

    更多信息在这里:https://docs.microsoft.com/en-us/aspnet/mvc/overview/getting-started/getting-started-with-ef-using-mvc/connection-resiliency-and-command-interception-with-the-entity-framework-in-an-asp-net-mvc-application

    1. @steliosalex:https://stackoverflow.com/a/22895703/3850405。请注意,更改每一列也可能不够。您还需要处理存储过程的元数据和参数,类似地获取数据库在创建它们时所具有的排序规则。 完全更改排序规则需要使用正确排序规则的创建数据库命令

    2. @RahmiAksu https://stackoverflow.com/a/31119371/3850405 注意:我认为这不是一个好的解决方案,但如果您使用它,请编辑第一个迁移。如果数据库已经在生产中,则不能使用。如果您有种子方法,则会抛出异常 Resetting the connection results in a different state than the initial login

    您的 Seed SqlException 可以通过使用普通的 ADO.Net 连接来解决,因此不会重置上下文的连接。但是如上所述,这可能会在以后导致很多错误。

    using (var conn = new SqlConnection(context.Database.Connection.ConnectionString))
    {
        using (var cmd = conn.CreateCommand())
        {
            cmd.CommandText = 
                string.Format("ALTER DATABASE [{0}] COLLATE Latin1_General_100_CI_AS",
                    context.Database.Connection.Database));
            conn.Open();
            cmd.ExecuteNonQuery();
        }
    }
    

    SqlException:重置连接会导致不同的状态 比初始登录。登录失败。用户 '' 登录失败。 无法继续执行,因为会话处于终止状态 状态。

    来源:

    https://stackoverflow.com/a/50400609/3850405

    【讨论】:

      【解决方案6】:

      使用当前版本的 EF (EF6) 根本不可能。但是,至少 EF6+ 现在可以使用已经存在的数据库。我们更改了部署方案,以便我们的部署脚本已经创建了数据库(包括默认排序规则),并让 EF6 使用现有数据库(使用正确的默认排序规则)。

      如果您绝对必须在代码中创建数据库并且不能使用除 EF 之外的任何其他内容(例如,您无法使用 ADO.NET 创建数据库),那么您必须选择 seliosalex 答案。这是我们提出的唯一解决方案,但是,请参阅我的评论,要做到这一点需要做很多工作。

      【讨论】:

        【解决方案7】:

        EF 5 现在支持使用 Code First 在现有数据库中创建缺失表,因此您可以创建一个空数据库并正确设置排序规则,然后再在其上运行 CF。

        【讨论】:

          【解决方案8】:
          1):
              public class DataBaseContext : System.Data.Entity.DbContext
              {
                  public DataBaseContext() : base("MyDB") { }
                  static DataBaseContext()
                  {
                      System.Data.Entity.Database.SetInitializer(new MyInitializer());
                  }
               ....
              }
          
          2):
              public class MyInitializer : DropCreateDatabaseIfModelChanges<DataBaseContext>
              {
                  public MyInitializer() { }
          
                  protected override void Seed(DataBaseContext context)
                  {
                      base.Seed(context);
                      using (var conn = new SqlConnection(context.Database.Connection.ConnectionString))
                      {
                          using (var cmd = conn.CreateCommand())
                          {
                              cmd.CommandText =
                                  string.Format("ALTER DATABASE [{0}] COLLATE Persian_100_CI_AS",
                                      context.Database.Connection.Database);
                              conn.Open();
                              cmd.ExecuteNonQuery();
                              conn.Close();
                          }
                          context.Database.Connection.Close();
                      }            
                  }
              }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-08-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多