【ASP.NET(C#)8】EntityFrameworkCoreの接続先をSQLServer(MSSQL)からPostgreSQLに変更

前回記事でいよいよ個人アプリのWeb公開が完了したわけですが、その際HerokuではSQLServerの利用が有料だったので無料であるPostgreSQLに変更しました。

今回は接続先をSQLServerからPostgreSQLに変更するまでの手順をご説明します。

※PostgreSQLのテーブル登録方法は前回記事に記載してあるので今回は割愛します。

EntityFrameworkcoreのPostgreSQLを追加インストール

最初にインストールしたEntityFrameworkCoreはSQLServer専用のものなのでPostgreSQL用は別に存在するのでそれを別途インストールします。

Program.csの接続部分を修正

上記Nugetパッケージの挿入が終わったらProgram.csの接続に使用するものをPostgreSQLに変更します。(UseNpgsql)

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using WebBBS_2.Data;
var builder = WebApplication.CreateBuilder(args);

//builder.Services.AddDbContext<WebBBSContext>(options =>
//    options.UseSqlServer(builder.Configuration.GetConnectionString("WebBBSContext")));

builder.Services.AddDbContext<WebBBSContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("WebBBSContext")));

// Add services to the container.
builder.Services.AddControllersWithViews();

var app = builder.Build();

~省略~

appsettings.jsonを修正

次に appsettings.json で実際にDBに接続する文字列を修正します。PostgreSQLで使用する接続する文字列は下記のようになります。ホスト・データベース名・ID・パスワードは実際にはHerokuでHeroku Postgresを利用した時に自動生成されたものを入れます。

"ConnectionStrings": {
  "WebBBSContext": "Server=ec2-xx-xxx-xxx-x.compute-1.amazonaws.com; Port=5432; Database=d6g38jmo75i445; User ID=user; Password=password"
}

あ。サイトによっては接続文字列がServer→Host で User ID→UserNameと説明しているサイトでまちまちですが両方を試した所、どちらでもOKでした。

"ConnectionStrings": {
  "WebBBSContext": "Host=ec2-xx-xxx-xxx-x.compute-1.amazonaws.com; Port=5432; Database=d6g38jmo75i445; UserName=user; Password=password"
}

一応注意してもらいたいのがローカルで確認する最中に appsettings.json を直したはずなのに実行すると修正前の文字列で接続されて失敗してしまうパターン。

この場合、プロジェクトの設定でDevelopment(開発版の設定)の値を参照してしまっているので画像のように隠れている appsettings. Development .json の接続文字列も同じように修正してください。

テーブル名はPostgreSQL の仕様に合わせ小文字にする

42P01: relation does not exist POSITON: 85

Not existなのでテーブルが存在しないというエラーです。今回は既に公開が終わっているテーブルを同じものを使用していますのでテーブル自体は存在するのですが、どうもPostgreSQL のテーブル名は大文字・小文字を区別する模様。

以上のように大文字で登録しても小文字で出来てしまう模様
Postgresqlのテーブル名が大文字だとややこしい件

C#の場合、コードファーストでEntityを作成する時はキャメルケースで作成してからMigrateする場合、テーブルもキャメルケースで作成されますのでPostgreSQLに対応する場合、修正する必要があります。

簡単に修正するには継承したContextのテーブル名を右クリックして名前の変更を押してから小文字に修正して右上の適用を押すと参照している部分も全て修正されます。

テーブルのカラムも小文字を参照するようにする

42703: column does not exist POSITON: 8

次はカラムでmigrateでテーブルを作った場合、カラムもキャメルになりますが、同じように修正します。こちらは[Column(“カラム名”)]を使うことにより、データベース側のカラム名を判別させることができます。

public class ArticleEntity
{
    [Column("id")]
    public int Id { get; set; }

    [Required]
    [Display(Name = "タイトル")]
    [Column("title")]
    public string? Title { get; set; }

    [Required]
    [Display(Name= "名前")]
    [Column("name")]
    public string? Name { get; set; }

    [Required]
    [Display(Name = "本文")]
    [Column("description")]
    public string? Description { get; set; }

    [Display(Name = "投稿日")]
    [Column("created")]
    public DateTime Created { get; set; }

    [Display(Name = "更新日")]
    [Column("updated")]
    public DateTime Updated { get; set; } = DateTime.Now;
}

一旦インデックスが表示される

ここまで来たら読み込みが完了します。次は登録ですね。

Postgresの日付はtimestampが通常でUTCのみ

Cannot write DateTime with Kind=Local to PostgreSQL type ‘timestamp with time zone’, only UTC is supported.

これはエラーの通りでPostgreSQLの時刻エリアに時刻を入れる時はUTCでなければいけません。ここが大きく違う所で、SQLServerとMySQLにはDateTime型があるのですが、PostgreSQLにはないのです。

時刻を設定する時はDateTime.Now から DateTime.UtcNow に修正し、デバッグ時ならKindがUtcになっていることを確認してください。

更新時も何故かKindがUtcではなくなる

これもデバッグ中にあった事ですが、一度UTCでデータを挿入しても読み込まれた時はUTCではなくなってしまうのでToUniversalTime()再度UTC型にしてあげないとエラーが出てしまいます。

実行前
実行後

.NET と Heroku上の時刻の登録のされ方が異なる

これは原因がわかりませんが、.NET上だと更新時 ToUniversalTime() を使う毎に9時間マイナスになってしまうので ToUniversalTime() .AddHours(9)で9時間足していたのですが、Heroku上ではその必要がありませんでした

表示も.NETでは 2022/02/21 と表示されますが、Heroku上では 02/21/2022 と表示されています。これはサーバ仕様なのかもしれませんね。

まとめ

EntityFrameCoreのPostgreSQLへの切り替えは楽ですが、データベース仕様に違い(特に時刻)があってそれに対応するのがちょっと大変ですね。

ちなみに実務ではSQLServerからMySQLへの切り替えも行ったのですがPrimaryKeyのサイズが255までという制約があるくらいで時刻に関してはSQLServerと同じでした。

まとめると、SQLServerからPostgreSQLへの切り替えは難しいです。.NETのクラウド先はやはりAzureが良さそうです(;^_^A