Case insensitive name of tables and properties in Entity Framework 7

前端 未结 6 1847
萌比男神i
萌比男神i 2021-01-03 00:26

I use Entity Framework 7 with Npgsql adapter. Sql generated by EF seems like

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

相关标签:
6条回答
  • 2021-01-03 01:01

    There's a very good reason Npgsql generates quotes everywhere - so you definitely should not remove them (even if it's technically possible as @natemcmaster says). Identifiers without quotes are automatically converted to lowercase by PostgreSQL. Entity Framework needs to be able to map C# properties to database columns, but C# properties are case-sensitive; so if you remove database case sensitivity you're shooting yourself in the foot...

    Unless you have a real problem (aside from the perceived ugliness) you should leave things as they are.

    0 讨论(0)
  • 2021-01-03 01:07

    I really don't really like having PascalCase identifiers in my PostgreSql database since I do a lot of manual querying directly against the database, so for my new .NET Core solution I kinda went to an extreme to change it.

    First, I defined my standard ApplicationDbContext using my PascalCase entity classes and marked it as abstract, then I created a PgDbContext specifically for my Postgres implementation.

    Next, I created a helper method like so:

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

    I then overrode some key methods by implementing some Npgsql related classes:

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

    Finally, wired up the IServiceCollection configuration like so:

            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>());
    

    With this, all my table names, columns, and constraints were named in snake_case instead of PascalCase, which meant I did not have to worry about using quoted identifiers in my manual queries. My entity classes are cased the way I like them, and my database names are also the way I like them.

    YMMV, but it works pretty swimmingly for me. It's important to note that while this does not actually remove the quotes from EF queries, it makes needing the quotes for manual queries disappear.

    0 讨论(0)
  • 2021-01-03 01:10

    As you can see in NpgsqlSqlGenerationHelper.cs:

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

    Npgsql thinks that identifiers that start with upper-case letter needs quoting. After a bit of thinking I implemented a solution described in https://andrewlock.net/customising-asp-net-core-identity-ef-core-naming-conventions-for-postgresql/ (converts all PascalCase identifiers to snake-case). It is a bit simplistic right now but I how EF Core soon will provide a way to define custom naming conventions.

    0 讨论(0)
  • 2021-01-03 01:12

    FYI version 2.1 of the Npgsql EF Core provider will only quote identifiers when that's needed (e.g. when they contain an uppercase letter).

    More importantly, everyone wanting snake case columns (or anything else besides the current behavior) can simply use the EF Core fluent API to manually specify whatever table and column name they want. It's also quite easy to write code that goes over all entities and properties, and automatically defines their database names by applying snake-case conversion or something else.

    This is better than changing any provider service, and will always work, whereas changing the SQL generation service (or any other) could be brittle.

    0 讨论(0)
  • 2021-01-03 01:15

    To do this, you would need to swap out the SQL generation service with your own, quote-less, lowercase version. To do this, you will need to understand how EF uses DI (try reading Understanding EF Services), and need to replace the service that generates SQL. In EF, this could be ISqlGenerationHelper, IMigrationsSqlGenerator, or IUpdateSqlGenerator depending on the circumstance..

    0 讨论(0)
  • 2021-01-03 01:21
    1. Override DelimitIdentifier in NpgsqlSqlGenerationHelper like this:

      public class SqlGenerationHelper : NpgsqlSqlGenerationHelper
      {
          public override string DelimitIdentifier(string identifier) => identifier.Contains(".") ? base.DelimitIdentifier(identifier) : identifier;
      }
      
    2. Replace ISqlGenerationHelper with your class using ReplaceService method:

      public class MyContext : DbContext
      {
          public virtual DbSet<MyTable> MyTable { get; set; }
      
          public MyContext(DbConnection connection) :
                 base(new DbContextOptionsBuilder().UseNpgsql(connection)
                                                   .ReplaceService<ISqlGenerationHelper, SqlGenerationHelper>()
                                                   .Options) 
          { }
      }
      
    0 讨论(0)
提交回复
热议问题