はじめに
ASP.NET Core MVC アプリから PostgreSQL に接続し、一覧画面を開いたところ、次の例外が発生しました。
PostgresException: 42P01: リレーション"Products"は存在しません
PostgreSQL 上にはデータベースもテーブルも存在しているのに、なぜこのエラーになるのか——原因は PostgreSQL の大文字小文字の扱い と EF Core のデフォルト命名 の不一致でした。
本記事では、実際に行った対処手順をまとめます。
環境
- ASP.NET Core MVC
- Entity Framework Core
- PostgreSQL
- Npgsql.EntityFrameworkCore.PostgreSQL
接続先(appsettings.json):
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5432;Database=sample_db;Username=postgres;Password=***"
}
DB 側には手動で作成した products テーブル(小文字)が存在していました。
発生したエラー
PostgresException: 42P01: リレーション"Products"は存在しません
42P01 は PostgreSQL の 「定義されたリレーション(テーブル等)が存在しない」 エラーです。
原因の整理:PostgreSQL は大文字小文字をどう扱うか
PostgreSQL では、識別子(テーブル名・カラム名)の扱いが次のように分かれます。
| SQL の書き方 | 実際の名前 | 大文字小文字 |
|---|---|---|
CREATE TABLE products (...) |
products |
引用符なし → 小文字に正規化 |
CREATE TABLE Products (...) |
products |
同上(結果は小文字) |
CREATE TABLE "Products" (...) |
Products |
引用符付き → 大小区別 |
EF Core(Npgsql)はデフォルトで、エンティティ名や DbSet 名から "Products"(ダブルクォート付き・大文字始まり) というテーブル名で SQL を発行します。
一方、手動作成したテーブルは products(小文字) です。
EF Core が探すもの: "Products"
DB にあるもの: products
→ 別物として扱われる → 42P01
「テーブルはあるのに存在しないと言われる」 のは、この名前の不一致が原因でした。
同様に、C# モデルのプロパティ名(PascalCase)と DB のカラム名(小文字)が一致しない場合も、 カラム不存在エラー になります。
対処 1:コンテキストでテーブル名を DB に合わせる
AppDbContext の DbSet 名や EF Core のデフォルト命名では "Products" を参照してしまうため、OnModelCreating で 実際のテーブル名 products を明示 しました。
修正前(イメージ)
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Product> Products { get; set; }
// → EF Core は "Products" テーブルを探しに行く
}
修正後
using Microsoft.EntityFrameworkCore;
namespace SampleApp.Data
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Product> Products { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// PostgreSQL 上の実テーブル名(小文字)を明示
modelBuilder.Entity<Product>().ToTable("products");
}
}
}
補足
-
DbSetプロパティ名(Products)と DB のテーブル名(products)は別物 です。 -
ToTable("products")により、生成される SQL が実 DB のテーブル名と一致します。 - 代替手段として、モデルクラスに
[Table("products")]を付ける方法もあります。
対処 2:Models に [Column] でカラム名を DB に合わせる
テーブル名を直したあと、今度は カラム名の不一致 が問題になりました。
| C# モデル(プロパティ名) | DB カラム名 |
|---|---|
Id |
id |
Name |
name |
Price |
price |
CreatedAt |
created_at |
EF Core はデフォルトでプロパティ名をそのままカラム名として使うため、PostgreSQL 側の小文字カラム名と一致しません。
修正後(Product.cs)
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace SampleApp.Models;
public class Product
{
[Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; } = String.Empty;
[Column("price")]
public int Price { get; set; }
[Column("created_at")]
public DateTime CreatedAt { get; set; }
}
System.ComponentModel.DataAnnotations.Schema の [Column("...")] で、C# 側のプロパティ名と DB 側のカラム名を対応付け できます。
代替手段
カラムマッピングは AppDbContext.OnModelCreating にまとめることもできます。
modelBuilder.Entity<Product>(entity =>
{
entity.ToTable("products");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.Name).HasColumnName("name");
entity.Property(e => e.Price).HasColumnName("price");
entity.Property(e => e.CreatedAt).HasColumnName("created_at");
});
属性方式と OnModelCreating 方式は どちらか一方 で十分です。
対応の流れ(まとめ)
- 一覧画面にアクセス →
42P01: リレーション"Products"は存在しません - DB には
productsテーブルがあることを確認 -
原因: EF Core が
"Products"を参照、DB はproducts(大小区別で不一致) -
対処:
AppDbContextにToTable("products")を追加 - 次の問題: カラム名も C#(PascalCase)と DB(小文字)で不一致
-
対処:
Productモデルに[Column("...")]を追加 - アプリ再起動 → 一覧画面が表示される
再発防止のヒント
手動でテーブルを作る場合
- PostgreSQL では 小文字・snake_case で統一するのが一般的
- EF Core 側は
ToTable/[Table]/[Column]/HasColumnNameで明示的にマッピングする
EF Core マイグレーションを使う場合
dotnet ef migrations add InitialCreate
dotnet ef database update
マイグレーションでスキーマを作れば、EF Core が期待するテーブル・カラム名と DB が自動的に揃います。
手動作成テーブル と EF マイグレーション を混在させると、今回のような不一致が起きやすいです。
DB の実態確認コマンド(psql)
\c sample_db
\d products
テーブル名・カラム名・型を、モデル定義と突き合わせると原因切り分けが早いです。
おわりに
42P01: リレーション"Products"は存在しません は、一見「テーブルが無い」ように見えますが、PostgreSQL の引用符付き識別子による大文字小文字の違い が原因であることが多いです。
今回の対応は次の 2 点でした。
-
AppDbContextでToTable("products")を指定(テーブル名の一致) -
Productモデルに[Column("...")]を追加(カラム名の一致)
手動で作った PostgreSQL テーブルを EF Core から使う場合は、命名規則の差を最初からマッピングで吸収しておくと安全です。