Entity Framework 7中表和属性的大小写不敏感名称

use*_*018 5 c# postgresql entity-framework npgsql entity-framework-core

我将Entity Framework 7与Npgsql适配器一起使用。EF生成的SQL看起来像

SELECT "r"."Id", "r"."Name" FROM "public"."Role" AS "r"
Run Code Online (Sandbox Code Playgroud)

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

CREATE TABLE "Role" (
    "Id" int,
    "Name" varchar(200)
);
Run Code Online (Sandbox Code Playgroud)

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

nac*_*man 7

这是 .NET Core 3.X 的紧凑解决方案 - Net5(可能也适用于 Net6)。这将假设所有表和列均为小写并带引号。如果有人命名与保留关键字(例如:“用户”、“角色”、“默认”、“注释”等)冲突的表/列,您会发现无条件引用很有帮助。

    /// <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));
    }
Run Code Online (Sandbox Code Playgroud)

将其插入非常简单:

optionsBuilder.UseNpgsql(...)
  .ReplaceService<ISqlGenerationHelper, NpgsqlSqlGenerationLowercasingHelper >();

Run Code Online (Sandbox Code Playgroud)


Jer*_*acs 5

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

首先,我ApplicationDbContext使用 PascalCase 实体类定义了我的标准并将其标记为抽象,然后我专门为我的 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();
    }
Run Code Online (Sandbox Code Playgroud)

然后,我通过实现一些 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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>());
Run Code Online (Sandbox Code Playgroud)

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

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


Sha*_*sky 5

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

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

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

  • 另请注意https://github.com/efcore/EFCore.NamingConventions,这是一个自动应用命名约定(例如snake_case)的插件 (2认同)

Sha*_*sky 1

Npgsql 到处生成引号是有充分理由的 - 所以你绝对不应该删除它们(即使它在技术上是可能的,正如 @natemcmaster 所说)。不带引号的标识符会被 PostgreSQL 自动转换为小写。实体框架需要能够将 C# 属性映射到数据库列,但 C# 属性区分大小写;因此,如果您删除数据库区分大小写的功能,那么您就是搬起石头砸自己的脚……

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

  • 还请考虑运营人员和其他想要使用您的应用程序数据库的人。在任何地方使用 `"` 非常烦人。默认情况下,几乎所有当前的 DB 都不区分大小写。此外,在同一个 C# 类上拥有两个仅大小写不同的属性也不是一个好习惯(而且它们也不能从例如签证基础) (8认同)
  • 对于任何想要小写标识符的人,请查看 https://github.com/efcore/EFCore.NamingConventions。这是一个 EF Core 插件,可以使你的表和列成为 Snake_case,这是 PostgreSQL 的通用标准。此时您不再需要引用它们,Npgsql 的 EF Core 提供程序也不会这样做。 (2认同)