C# Entity Framework 7中表和属性的名称不区分大小写

C# Entity Framework 7中表和属性的名称不区分大小写,c#,entity-framework,postgresql,entity-framework-core,npgsql,C#,Entity Framework,Postgresql,Entity Framework Core,Npgsql,我使用实体框架7和Npgsql适配器。EF生成的Sql看起来像 SELECT "r"."Id", "r"."Name" FROM "public"."Role" AS "r" 而且它不适用于博士后,因为区分大小写的策略。要使其工作,我需要编写创建表脚本 CREATE TABLE "Role" ( "Id" int, "Name" varchar(200) ); 但是很难看。有没有办法让EF生成不带引号的脚本或使用小写命名样式的脚本?要做到这一点,您需要用自己的、不带引号的小写

我使用实体框架7和Npgsql适配器。EF生成的Sql看起来像

SELECT "r"."Id", "r"."Name" FROM "public"."Role" AS "r"
而且它不适用于博士后,因为区分大小写的策略。要使其工作,我需要编写创建表脚本

CREATE TABLE "Role" (
    "Id" int,
    "Name" varchar(200)
);

但是很难看。有没有办法让EF生成不带引号的脚本或使用小写命名样式的脚本?

要做到这一点,您需要用自己的、不带引号的小写版本替换SQL生成服务。要做到这一点,您需要了解EF如何使用DI(尝试读取),并需要替换生成SQL的服务。在EF中,这可能是
ISqlGenerationHelper
imiglationSqlGenerator
,或者
IUpdateSqlGenerator
,这取决于具体情况。

Npgsql有一个很好的理由在任何地方生成引号-因此您绝对不应该删除它们(即使@natemcmaster说在技术上是可能的)。PostgreSQL会自动将不带引号的标识符转换为小写。实体框架需要能够将C#属性映射到数据库列,但C#属性区分大小写;所以,如果你删除数据库区分大小写,你就是在自食其果

除非你有真正的问题(除了感觉到的丑陋),否则你应该保持现状

  • 重写NpgsqlSqlGenerationHelper中的标识符,如下所示:

    public class SqlGenerationHelper : NpgsqlSqlGenerationHelper
    {
        public override string DelimitIdentifier(string identifier) => identifier.Contains(".") ? base.DelimitIdentifier(identifier) : identifier;
    }
    
  • 使用ReplaceService方法将ISqlGenerationHelper替换为您的类:

    public class MyContext : DbContext
    {
        public virtual DbSet<MyTable> MyTable { get; set; }
    
        public MyContext(DbConnection connection) :
               base(new DbContextOptionsBuilder().UseNpgsql(connection)
                                                 .ReplaceService<ISqlGenerationHelper, SqlGenerationHelper>()
                                                 .Options) 
        { }
    }
    
    公共类MyContext:DbContext
    {
    公共虚拟数据库集MyTable{get;set;}
    公共MyContext(数据库连接):
    基本(新的DbContextOptionsBuilder().UseNpgsql(连接)
    .ReplaceService()
    (可选)
    { }
    }
    

  • 我真的不喜欢PostgreSql数据库中有PascalCase标识符,因为我直接对数据库进行了大量手动查询,所以对于新的.NET核心解决方案,我有点极端地改变了它

    首先,我使用PascalCase实体类定义了我的标准
    ApplicationDbContext
    ,并将其标记为抽象,然后我专门为我的Postgres实现创建了一个PgDbContext

    接下来,我创建了一个助手方法,如下所示:

        public static string FromPascalCaseToSnakeCase(this string str)
        {
            return string.IsNullOrWhiteSpace(str) ? str : string.Concat(str.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())).ToLower();
        }
    
    然后,我通过实现一些与Npgsql相关的类来覆盖一些关键方法:

    public class LowercaseSqlGenerationHelper : RelationalSqlGenerationHelper
    {
        public LowercaseSqlGenerationHelper(RelationalSqlGenerationHelperDependencies dependencies) : base(dependencies)
        {
        }
    
        public override void DelimitIdentifier(StringBuilder builder, string identifier)
        {
            base.DelimitIdentifier(builder, identifier.FromPascalCaseToSnakeCase());
        }
    
        public override void DelimitIdentifier(StringBuilder builder, string name, string schema)
        {
            base.DelimitIdentifier(builder, name.FromPascalCaseToSnakeCase(), schema.FromPascalCaseToSnakeCase());
        }
    
        public override string DelimitIdentifier(string identifier)
        {
            return base.DelimitIdentifier(identifier.FromPascalCaseToSnakeCase());
        }
    
        public override string DelimitIdentifier(string name, string schema)
        {
            return base.DelimitIdentifier(name.FromPascalCaseToSnakeCase(), schema.FromPascalCaseToSnakeCase());
        }
    }
    
    public class LowercaseQuerySqlGenerator : NpgsqlQuerySqlGenerator
    {
        public LowercaseQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies, RelationalSqlGenerationHelperDependencies rSGenDep, SelectExpression selectExpression) : 
            base(
                new QuerySqlGeneratorDependencies(dependencies.CommandBuilderFactory, 
                    new LowercaseSqlGenerationHelper(rSGenDep), 
                    dependencies.ParameterNameGeneratorFactory, 
                    dependencies.RelationalTypeMapper)
                , selectExpression)
        {
        }
    }
    
    public class LowercaseHistoryRepository:NpgsqlHistoryRepository
    {
        public LowercaseHistoryRepository(HistoryRepositoryDependencies dependencies) : base(dependencies)
        {
        }
    
        protected override string ExistsSql
        {
            get
            {
                var builder = new StringBuilder();
    
                builder.Append("SELECT EXISTS (SELECT 1 FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace WHERE ");
    
                if (TableSchema != null)
                {
                    builder
                        .Append("n.nspname='")
                        .Append(SqlGenerationHelper.EscapeLiteral(TableSchema.FromPascalCaseToSnakeCase()))
                        .Append("' AND ");
                }
    
                builder
                    .Append("c.relname='")
                    .Append(SqlGenerationHelper.EscapeLiteral(TableName.FromPascalCaseToSnakeCase()))
                    .Append("');");
    
                return builder.ToString();
            }
        }
    }
    
    最后,将
    IServiceCollection
    配置连接起来,如下所示:

            services.AddDbContext<PgDbContext>(
                options =>
                {
                    options.UseNpgsql(config.GetSection("ConnectionStrings:ApplicationContext").Value)
                        .ReplaceService<ISqlGenerationHelper, LowercaseSqlGenerationHelper>()
                        .ReplaceService<IQuerySqlGenerator, LowercaseQuerySqlGenerator>()
                        .ReplaceService<IHistoryRepository, LowercaseHistoryRepository>();
                },
                ServiceLifetime.Scoped);
            services.AddScoped<ApplicationDbContext>(di => di.GetService<PgDbContext>());
    
    services.AddDbContext(
    选项=>
    {
    options.UseNpgsql(config.GetSection(“ConnectionString:ApplicationContext”).Value)
    .ReplaceService()
    .ReplaceService()
    .ReplaceService();
    },
    ServiceLifetime.Scoped);
    services.addScope(di=>di.GetService());
    
    这样,我所有的表名、列和约束都以snake_的形式命名,而不是以PascalCase命名,这意味着我不必担心在手动查询中使用带引号的标识符。我的实体类是按我喜欢的方式大小写的,我的数据库名称也是按我喜欢的方式命名的

    YMMV,但它对我来说非常顺利。需要注意的是,虽然这实际上并没有从EF查询中删除引号,但它会使手动查询所需的引号消失。

    如您所见:


    Npgsql认为以大写字母开头的标识符需要引用。经过一番思考,我实现了(将所有PascalCase标识符转换为snake-case)中描述的解决方案。现在有点简单,但我知道EF Core很快将提供一种定义自定义命名约定的方法。

    Npgsql EF Core provider的2.1版仅供参考,仅在需要时引用标识符(例如,当标识符包含大写字母时)

    更重要的是,每个想要snake-case列(或当前行为之外的任何内容)的人都可以简单地使用EF Core fluent API手动指定他们想要的任何表和列名。编写遍历所有实体和属性的代码,并通过应用snake-case转换或其他方法自动定义它们的数据库名称,也非常容易


    这比更改任何提供程序服务都要好,而且总是有效的,而更改SQL生成服务(或任何其他服务)可能很脆弱。

    这里有一个针对.NET Core 3.X的紧凑解决方案(可能在5.X中有效,不确定)。这将假定所有的表和列都是小写的,并用引号引起来。如果有人命名的表/列与保留关键字(例如:“user”、“role”、“default”、“comment”等)冲突,您会发现无条件引用很有用

    插入它非常简单:

    optionsBuilder.UseNpgsql(...)
      .ReplaceService<ISqlGenerationHelper, CustomNameSqlGenerationHelper>();
    
    
    optionsBuilder.UseNpgsql(…)
    .ReplaceService();
    
    请考虑运营部和其他希望使用您的应用程序数据库的人。在任何地方使用
    都非常烦人。默认情况下,几乎所有当前的DBs都不区分大小写。而且,在同一个C#类上有两个仅在大小写上不同的属性也不是一个好的做法(它们也不能从例如VisaulBasic中使用)>但是C#属性是区分大小写的,每个序列化程序都能做到这一点,这不是一个真正的论点,我非常同意前面的评论,不得不使用引号对于Ops.NET Core 2.1来说是一团糟。不过,上面只是2.0的一个解决方案。这对我来说是最好的解决方案。谢谢这比您的原始答案(是的,我们知道SQL为什么会被引用,但事实上这是一个向后的解决方案)但没有被提升,因为您没有给出示例。另外,请注意,这是一个自动应用命名约定(如snake_case)的插件
        public class CustomNameSqlGenerationHelper : RelationalSqlGenerationHelper
        {
            private static string Customize(string input) => input.ToLower();
            public CustomNameSqlGenerationHelper([NotNull] RelationalSqlGenerationHelperDependencies dependencies) : base(dependencies) { }
            public override string DelimitIdentifier(string identifier) => base.DelimitIdentifier(Customize(identifier));
            public override void DelimitIdentifier(StringBuilder builder, string identifier) => base.DelimitIdentifier(builder, Customize(identifier));
        }
    
    optionsBuilder.UseNpgsql(...)
      .ReplaceService<ISqlGenerationHelper, CustomNameSqlGenerationHelper>();