23

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

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

4

8 回答 8

10

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

这绝对是可能的,尽管它有点小技巧。您可以使用命令拦截器更改 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) { }
}

评论

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

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

于 2017-03-03T10:41:27.460 回答
7

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

  1. Add-Migration [YourCustomMigration]通过在包管理器控制台中键入来创建迁移代码。(代码优先迁移
  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从那时起发出的每个命令都会创建一个新的迁移类。所有迁移代码都按顺序执行。

于 2015-06-29T15:15:54.760 回答
7

我使用EFCore 2.1的解决方案是从 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>();
    }

    //...
}
于 2019-03-21T10:04:05.723 回答
5

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

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

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

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

希望有帮助。

于 2014-04-06T15:00:30.903 回答
4

我想解释一下为什么你不应该为此使用种子方法。如果在添加任何列后更改数据库排序规则,则存在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或 inApplication_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"));
    }
}

我也不会从接口继承,而是使用DbCommandInterceptorMicrosoft 在他们的示例中所做的实现。

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-与-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则会引发异常。

您的 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

于 2018-05-18T09:58:48.117 回答
1

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

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

于 2014-04-06T15:26:54.333 回答
0

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

于 2012-11-09T11:17:47.827 回答
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();
            }            
        }
    }
于 2019-08-11T12:06:18.337 回答