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.