【问题标题】:Case insensitive name of tables and properties in Entity Framework 7Entity Framework 7 中表和属性的不区分大小写的名称
【发布时间】:2016-06-25 04:21:51
【问题描述】:

我使用带有 Npgsql 适配器的 Entity Framework 7。 EF生成的sql好像

SELECT "r"."Id", "r"."Name" FROM "public"."Role" AS "r"

它在 Postgres 中不起作用,因为区分大小写的策略。为了使它工作,我需要编写创建表脚本

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

但这很丑。有没有办法让 EF 生成不带引号或小写命名风格的脚本?

【问题讨论】:

    标签: c# entity-framework postgresql entity-framework-core npgsql


    【解决方案1】:
    1. 像这样覆盖 NpgsqlSqlGenerationHelper 中的 DelimitIdentifier:

      public class SqlGenerationHelper : NpgsqlSqlGenerationHelper
      {
          public override string DelimitIdentifier(string identifier) => identifier.Contains(".") ? base.DelimitIdentifier(identifier) : identifier;
      }
      
    2. 使用 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) 
          { }
      }
      

    【讨论】:

      【解决方案2】:

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

          /// <summary>A replacement for <see cref="NpgsqlSqlGenerationHelper"/>
          /// to convert PascalCaseCsharpyIdentifiers to alllowercasenames.
          /// So table and column names with no embedded punctuation
          /// get generated with no quotes or delimiters.</summary>
          public class NpgsqlSqlGenerationLowercasingHelper : NpgsqlSqlGenerationHelper
          {
              //Don't lowercase ef's migration table
              const string dontAlter="__EFMigrationsHistory";
              static string Customize(string input) => input==dontAlter? input : input.ToLower();
              public NpgsqlSqlGenerationLowercasingHelper(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, NpgsqlSqlGenerationLowercasingHelper >();
      
      

      【讨论】:

      • 男人!你真的毁了我的生活.. 谢谢!
      • 如果可以的话,我会给予双重支持 :-) 我从 NpgsqlGenerationHelper 继承的 Postgres 的 nb 因为那个助手知道 postgres 保留字和特殊字符
      【解决方案3】:

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

      首先,我使用我的 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>());
      

      有了这个,我所有的表名、列和约束都以 snake_case 而不是 PascalCase 命名,这意味着我不必担心在手动查询中使用带引号的标识符。我的实体类按照我喜欢的方式大小写,我的数据库名称也是我喜欢的方式。

      YMMV,但它对我来说非常有效。请务必注意,虽然这实际上并没有从 EF 查询中删除引号,但它使手动查询所需的引号消失了。

      【讨论】:

      • .NET Core 2.1 确实在这里改变了一些东西,不过……以上只是 2.0 的解决方案。
      • 这对我来说是最好的解决方案。谢谢
      【解决方案4】:

      正如您在NpgsqlSqlGenerationHelper.cs 中看到的那样:

      static bool RequiresQuoting(string identifier)
      {
              var first = identifier[0];
              if (!char.IsLower(first) && first != '_')
                  return true;
      

      Npgsql 认为以大写字母开头的标识符需要引用。经过一番思考,我实现了https://andrewlock.net/customising-asp-net-core-identity-ef-core-naming-conventions-for-postgresql/ 中描述的解决方案(将所有 PascalCase 标识符转换为蛇形大小写)。现在有点简单,但我知道 EF Core 很快将提供一种定义自定义命名约定的方法。

      【讨论】:

        【解决方案5】:

        为此,您需要将 SQL 生成服务替换为您自己的无引号小写版本。为此,您需要了解 EF 如何使用 DI(尝试阅读 Understanding EF Services),并且需要替换生成 SQL 的服务。在 EF 中,这可能是 ISqlGenerationHelperIMigrationsSqlGeneratorIUpdateSqlGenerator,具体取决于具体情况。

        【讨论】:

          【解决方案6】:

          仅供参考,Npgsql EF Core 提供程序的 2.1 版只会在需要时引用标识符(例如,当它们包含大写字母时)。

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

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

          【讨论】:

          • 这比您的原始答案要好得多(是的,我们知道 为什么 SQL 被引用,但实际上它是一个向后的解决方案)但没有被赞成,因为您没有给出示例.
          • 另请注意github.com/efcore/EFCore.NamingConventions,这是一个自动应用诸如snake_case之类的命名约定的插件
          【解决方案7】:

          Npgsql 到处生成引号是有充分理由的——所以你绝对不应该删除它们(即使正如@natemcmaster 所说,它在技术上是可行的)。不带引号的标识符会被 PostgreSQL 自动转换为小写。 Entity Framework 需要能够将 C# 属性映射到数据库列,但 C# 属性区分大小写;因此,如果您删除数据库区分大小写,您就是在自取其辱......

          除非你有真正的问题(除了被认为的丑陋),否则你应该保持现状。

          【讨论】:

          • 还请考虑 Ops 和其他想要使用您的应用程序数据库的人。在任何地方使用" 非常烦人。默认情况下,几乎所有当前的数据库都不区分大小写。此外,在同一个 C# 类上拥有两个仅在大小写上有所不同的属性也不是一个好习惯(它们也不能从例如 VisaulBasic 中使用)
          • > 但 C# 属性区分大小写每个序列化程序都能够管理,这不是一个真正的论点而且我不能更同意前面的评论,必须使用引号是一团糟操作
          • 对于任何想要小写标识符的人,请查看github.com/efcore/EFCore.NamingConventions。这是一个 EF Core 插件,它可以使您的表和列成为蛇形大小写,这是 PostgreSQL 的通用标准。此时您不再需要引用它们,并且 Npgsql 的 EF Core 提供程序不会这样做。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-07-04
          • 2016-11-24
          • 2012-05-24
          • 1970-01-01
          相关资源
          最近更新 更多