在Entity Framework Code-First Initializer中设置数据库排序规则

时间:2012-08-21 12:33:31

标签: sql-server entity-framework-4.1 ef-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.

有人可以建议我如何设置校对吗?

8 个答案:

答案 0 :(得分:7)

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

这绝对是可能的,虽然这有点像黑客。您可以使用命令拦截器更改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) { }
}

说明

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

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

答案 1 :(得分:4)

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

  1. 通过在程序包管理器控制台中键入Add-Migration [YourCustomMigration]来创建迁移代码。 (Code First Migrations
  2. 第一步应该使用Up()覆盖中的当前模型创建代码创建迁移类。在表创建代码之前添加ALTER DATABASE代码,以便使用所需的数据库归类创建它们。另请注意suppressTransaction标志:
  3. public override void Up() { Sql("ALTER DATABASE [YourDB] COLLATE [YourCollation]", suppressTransaction: true); [...Your DB Objects Creation codes here...] }

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

答案 2 :(得分:2)

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

  1. 看来EF使用服务器默认创建数据库 整理,所以你可以做的就是改变它。
  2. 您无法在Seed()方法中更改数据库排序规则 但您可以更改表的各列的排序规则 (注意:没有表格整理这样的东西,它确实与之相关 表中的列)。您必须更改每列的排序规则 分开。
  3. 如果您正在使用迁移,则可以更改表列 您Up()方法中的排序规则。
  4. 当您使用Seed()方法时,我会在Seed()方法中建议以下内容(根据需要进行修改):

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

    希望有所帮助。

答案 3 :(得分:2)

我想解释为什么你不应该使用种子方法。如果在添加任何列后更改数据库归类,则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'

enter image description here

因此,您需要在添加任何列之前更改数据库归类或分别更改每个列。可能的解决方案:

  1. @MathieuRenda https://stackoverflow.com/a/42576705/3850405
  2. 我会根据文档中的建议,将DbInterception.Add放在DbConfigurationApplication_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"));
        }
    }
    

    我也不会继承接口,而是使用Microsoft在其示例中所做的DbCommandInterceptor实现。

    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。请注意,更改每列可能也不够。您还需要处理存储过程的元数据和参数,并获得数据库在创建这些数据时所具有的排序规则。 完全更改排序规则需要具有正确排序规则的create database命令

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

    3. 您的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:重置连接会导致其他状态不同   比初次登录。登录失败。用户''登录失败。   无法继续执行,因为会话处于kill状态   状态。

      来源:

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

我使用 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>();
    }

    //...
}

答案 6 :(得分:0)

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

答案 7 :(得分:0)

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