EntityFramework を用いて DB を作成した時は、基本的にメンバー名と同じ名前(通常は PascalCase)でテーブルやカラムが作成されます。
基本的にはこれでも問題はありませんが、DB に関する命名規則が定められている場合などはこれでは困ることがあります。
そこで、メンバー名を変更せずに作成されるテーブルやカラムの名前を変更する方法のメモを残しておきます。
確認時のバージョン情報
- .NET: 9.0.200
- Microsoft.EntityFrameworkCore.SQLite: 9.0.9
- EFCore.NamingConventions: 9.0.0
動作確認用サンプルコード
動作確認用に以下の様な簡単なコードを作成してみます。
DB をメモリ上に作成して、その時に発行される SQL 文を確認します。
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using var connection = new SqliteConnection("Filename=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<SampleDbContext>()
.UseSqlite(connection)
.LogTo(Console.WriteLine, LogLevel.Information)
.Options;
using var context = new SampleDbContext(options);
context.Database.EnsureCreated();
file sealed class SampleDbContext(DbContextOptions options) : DbContext(options) {
public DbSet<Entity> Entities => this.Set<Entity>();
public DbSet<EntityGroup> EntityGroups => this.Set<EntityGroup>();
}
file sealed class Entity {
public int Id { get; init; }
public string Name { get; set; } = string.Empty;
public int? GroupId { get; set; }
public EntityGroup? Group { get; set; }
}
file sealed class EntityGroup {
public int Id { get; init; }
public string Name { get; set; } = string.Empty;
public List<Entity> Entities { get; set; } = [];
}
特に何も指定しなければ以下の様な SQL が発行されます。
CREATE TABLE "EntityGroups" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_EntityGroups" PRIMARY KEY AUTOINCREMENT,
"Name" TEXT NOT NULL
);
CREATE TABLE "Entities" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Entities" PRIMARY KEY AUTOINCREMENT,
"Name" TEXT NOT NULL,
"GroupId" INTEGER NULL,
CONSTRAINT "FK_Entities_EntityGroups_GroupId" FOREIGN KEY ("GroupId") REFERENCES "EntityGroups" ("Id")
);
CREATE INDEX "IX_Entities_GroupId" ON "Entities" ("GroupId");
方法 (A):個別に指定する
最も基本的かつシンプルな手段は、DataAnnotations や FluentAPI を使用して各テーブルやプロパティに対して明示的に名前を指定する方法です。
それぞれに明示的に、またメンバー名とは独立した名前を指定することができます。
一方で個別に指定しなければならないため手間が多いため「基本的に後述する一括構成で対応して特殊な箇所のみ個別指定する」等といった対応がよさそうです。
以下は IEntityTypeConfiguration (FluentAPI) を使用してテーブル、カラムおよびキーの名前を指定する例になります。
// 変更箇所以外は省略
using Microsoft.EntityFrameworkCore.Metadata.Builders;
file sealed class SampleDbContext(DbContextOptions options) : DbContext(options) {
// "OnModelCreating" で指定
protected override void OnModelCreating(ModelBuilder modelBuilder) =>
modelBuilder.ApplyConfiguration(new EntityConfiguration());
}
file sealed class EntityConfiguration : IEntityTypeConfiguration<Entity> {
public void Configure(EntityTypeBuilder<Entity> builder) {
_ = builder.ToTable("entities");
_ = builder.HasKey(e => e.Id)
.HasName("pk_entities");
_ = builder
.HasOne(e => e.Group)
.WithMany(e => e.Entities)
.HasForeignKey(e => e.GroupId)
.HasPrincipalKey(e => e.Id)
.HasConstraintName("fk_entities_entity_groups_group_id");
_ = builder.Property(e => e.Name)
.HasColumnName("name");
_ = builder.Property(e => e.GroupId)
.HasColumnName("group_id");
}
}
CREATE TABLE "EntityGroups" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_EntityGroups" PRIMARY KEY AUTOINCREMENT,
"Name" TEXT NOT NULL
);
CREATE TABLE "entities" (
"Id" INTEGER NOT NULL CONSTRAINT "pk_entities" PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"group_id" INTEGER NULL,
CONSTRAINT "fk_entities_entity_groups_group_id" FOREIGN KEY ("group_id") REFERENCES "EntityGroups" ("Id")
);
CREATE INDEX "IX_entities_group_id" ON "entities" ("group_id");
方法 (B):IConversion を実装して一括で変換する
IEntityTypeAddedConvention, IPropertyAddedConvention 等のインターフェースを実装したクラスを作成することで、テーブル名やカラム名などを一括で変換することができます。
この方法では名前だけでなく DB 上のデータ型なども一括構成することが可能ですが、如何せん情報が少なく名前の一括変換ならば更に後述するライブラリで実現可能なため、これらのインターフェースを独自に実装する機会はあまり無さそう…?
以下はとりあえずテーブル名だけを対象に snake_case へ変換する例になります。
// 変更箇所以外は省略
using System.Globalization;
using System.Text;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
file sealed class SampleDbContext(DbContextOptions options) : DbContext(options) {
// "ConfigureConventions" で指定
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) =>
configurationBuilder.Conventions.Add(_ => new SnakeCaseNamingConversion());
}
// とりあえずテーブル名だけに対応。
// カラム名などにも対応する場合は IPropertyAddedConvention, IKeyAddedConvention 等も実装する。
file sealed class SnakeCaseNamingConversion : IEntityTypeAddedConvention {
public void ProcessEntityTypeAdded(
IConventionEntityTypeBuilder entityTypeBuilder,
IConventionContext<IConventionEntityTypeBuilder> context
) {
var metadata = entityTypeBuilder.Metadata;
if(metadata.GetTableName() is { } tableName) {
tableName = GetSnakeCase(tableName);
_ = entityTypeBuilder.ToTable(tableName, metadata.GetSchema());
}
}
private static string GetSnakeCase(string value) {
// とりあえず簡単に実装
var builder = new StringBuilder();
var culture = CultureInfo.InvariantCulture;
for(var i = 0; i < value.Length; i++) {
var c = value[i];
if(char.IsUpper(c)) {
if(i is not 0) {
_ = builder.Append('_');
}
_ = builder.Append(char.ToLower(c, culture));
} else {
_ = builder.Append(c);
}
}
return builder.ToString();
}
}
CREATE TABLE "entity_groups" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_entity_groups" PRIMARY KEY AUTOINCREMENT,
"Name" TEXT NOT NULL
);
CREATE TABLE "entities" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_entities" PRIMARY KEY AUTOINCREMENT,
"Name" TEXT NOT NULL,
"GroupId" INTEGER NULL,
CONSTRAINT "FK_entities_entity_groups_GroupId" FOREIGN KEY ("GroupId") REFERENCES "entity_groups" ("Id")
);
CREATE INDEX "IX_entities_GroupId" ON "entities" ("GroupId");
方法 (C):EFCore.NamingConventions を使用して一括で変換する
生成されるテーブルやカラム、キー等の名前を snake_case や UPPERCASE 等の指定された形式に変換してくれるライブラリです。
現在対応している形式は snake_case, SNAKE_CASE, camelCase, lowercase, UPPERCASE の 5 つのようです。
(内部的には前述の IEntityTypeAddedConvention 等が使用されているらしい)
以下はそれぞれの名前を snake_case に変換する例になります。
// 変更箇所以外は省略
file sealed class SampleDbContext(DbContextOptions options) : DbContext(options) {
// "OnConfiguring" で指定
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder.UseSnakeCaseNamingConvention();
}
CREATE TABLE "entity_groups" (
"id" INTEGER NOT NULL CONSTRAINT "pk_entity_groups" PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL
);
CREATE TABLE "entities" (
"id" INTEGER NOT NULL CONSTRAINT "pk_entities" PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"group_id" INTEGER NULL,
CONSTRAINT "fk_entities_entity_groups_group_id" FOREIGN KEY ("group_id") REFERENCES "entity_groups" ("id")
);
CREATE INDEX "ix_entities_group_id" ON "entities" ("group_id");