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

问题描述 投票:17回答:7

我想在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.

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

sql-server entity-framework-4.1 ef-code-first
7个回答
7
投票

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

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

Before database creation

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

The interceptor

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

Remarks

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

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


4
投票

我能够使用自定义迁移(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命令都会创建一个新的迁移类。所有迁移代码都按顺序执行。


2
投票

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

  1. EF似乎使用服务器默认排序规则创建数据库,因此您可以做的一件事就是更改它。
  2. 您无法在Seed()方法中更改数据库排序规则,但可以更改表的各个列的排序规则(注意:没有表格排序规则,它与表格中的列有关)。您必须单独更改每列的排序规则。
  3. 如果您使用迁移,则可以更改Up()方法中的表列排序规则。

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

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

希望有所帮助。


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

我会把DbInterception.Add放在DbConfigurationApplication_StartGlobal.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的实现,正如微软在他们的例子中所做的那样。

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

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

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


2
投票

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

    //...
}

1
投票

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

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


0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.