WebSurfer's Home

Filter by APML

PostgreSQL で EF6 DB First

by WebSurfer 22. July 2022 14:26

PostgreSQL の既存のデータベースから Visual Studio 2022 の ADO.NET Entity Data Model ウィザードで Entity Data Model (EDM) を作成し、ASP.MET MVC5 アプリで利用する話を書きます。ウィザードが期待通り働かず紆余曲折ありました。そのあたりを以下に詳しく書きましたので興味があれば読んでください。

PostgresSQL で EF6 DB First

ASP.NET Core + EF Core ではなく、.NET Framework ベースの ASP.NET MVC5 で Entity Framework 6 を利用する話ですので注意してください。(ちなみに、EF Core では ADO.NET Entity Data Model ウィザードは使えません)

環境は以下の通りで、すべてこの記事を書いた時点での最新版です。

  • PostgreSQL 14.4
  • Visual Studio Community 2022 17.2.6
  • Npgsql PostgreSQL Integration 4.1.12
  • Entityframework6.Npgsql 6.4.3
  • Npgsql 6.0.5
  • .NET Framework 4.8
  • ASP.NET MVC 5.2.7 (VS2022 のテンプレートで作成)

PosrgreSQL は先の記事「PostgreSQL をインストールしました」に書いた自分の Windows 10 PC にインストールしたものです。

Npgsql PostgreSQL Integration は Visual Studio の拡張機能で、その中に "Create an Entity Framework 6 model from an existing database" という機能があって、ADO.NET Entity Data Model ウィザードで EDM を作成するには必須のようです。バージョン 4.1.12 は Visual Studio 2022 用にリリースされたものだそうです。

Npgsql PostgreSQL Integration

Entityframework6.Npgsql 6.4.3 と Npgsql 6.0.5 は NuGet からインストールします。これをインストールしないとデータソースのメニューの中に PostgreSQL Database が表示されないようです。

NuGet パッケージ

試しにまず .NET Framework 4.8 のコンソールアプリで EDM を作成してみました。

Visual Studio 2022 でコンソールアプリプロジェクトを作成し Entityframework6.Npgsql 6.4.3 と Npgsql 6.0.5 をインストールします。

ソリューションエクスプローラーでプロジェクトを右クリックして[追加(D)]⇒[新しい項目(W)...]で表示される「新しい項目の追加」画面で[ADO.NET Entity Data Model]を選択してウィザードを起動します。

ADO.NET Entity Data Model

「モデルのコンテンツの選択」画面が表示されるので[データベースから EF Designer]を選択して[次へ(N) >]をクリックします。(注: [データベースから Code First]を選択するのは DB First としては正しくないです)

モデルのコンテンツの選択

「データ接続の選択」画面が表示されるので[新しい接続]ボタンをクリックします。

データ接続の選択

「接続の変更」画面が表示されるのでその[変更]ボタンをクリックすると、下のような「データソースの変更」画面が表示されるので PostgreSQL Database を選択して[OK]ボタンをクリックします。

データソースの変更

「接続のプロパティ」画面で接続する PostgreSQL のサーバー名、ポート、データベース名などを入力して[OK]ボタンをクリックします。

接続のプロパティ

「データベースオブジェクトと設定の選択」画面で使用するテーブル(この例では Movie テーブル)にチェックを入れ[完了]をクリックすれば EDM(.edmx などのファイル)が生成されます。

データベースオブジェクトと設定の選択

以上、コンソールアプリでは問題なく EDM を作成できることが確認できたので、Visual Studio 2022 のテンプレートで .NET Framework 4.8 の ASP.NET MVC 5.2.7 ソリューション/プロジェクトを認証なしで作成し、それに上記と同様な手順で EDM の追加をトライしてみました。

コンソールアプリの時と同様に楽勝・・・だと思っていたのですがダメでした。(涙)

Entity Data Model ウィザードの最初の画面で[データベースから EF Designer]を選択して[次へ(N) >]をクリックすると、なぜか「新しい項目の追加」メニューに戻ってしまいます。

いろいろ試したのですが何をどうしてもダメでした。原因不明で今のところ解決策を見つけることができていません。

やむを得ないので、同じソリューション内に別プロジェクトでクラスライブラリを追加し、上に書いたコンソールアプリの手順と同様にして EDM を作りました。その結果が以下の画像です。

クラスライブラリと EDM

ただし、この時もクラスライブラリに Entityframework6.Npgsql 6.4.3, Npgsql 6.0.5 をインストールしてから、続けて EDM を生成しようとすると上に書いたのと同様に「新しい項目の追加」メニューに戻ってしまうという問題が出ました。Visual Studio を一旦終了して再度立ち上げるとその問題は出なくなり EDM は作成できたのですが、どうしてなのかこれも原因不明です。

EDM が完成したらクラスライブラリを ASP.NET MVC5 プロジェクトで参照に追加し、クラスライブラリの App.config に生成された接続文字列を MVC アプリの web.congfig にコピーします。接続文字列のコピーは忘れないようにしてください。

ソリューションをリビルドしてから「新規スキャフォールディングアイテムの追加」操作で CRUD 用の Controller/View を自動的に一式生成できます。

新規スキャフォールディングアイテムの追加

その実行結果がこの記事の一番上の画像です。Index(一覧表示)だけでなく Create, Edit, Details, Delete も期待通りに動きます。

以上ですが、そのあともう一つ不可解な現象がありました。上の操作が完了した後なら ASP.NET MVC5 プロジェクトでも「Entity Data Model ウィザード」画面から先に進めるようになったということです。

Npgsql PostgreSQL Integration 4.1.12 は Visual Studio 2022 用に 6 月にリリースされたばかりということで、動作が不安定なのかもしれません。どうしても .NET Framework ベースでなければならないということでなければ、上に書いたような不可解な動きはない .NET 6.0 の ASP.NET Core MVC の方向に進むのがよさそうです。


さて、次はコードファーストです。これも予期してなかった問題がありました。続けてこの記事に書くと長くなりすぎるので別の記事に書くことにします。

Tags: , , , ,

MVC

ASP.NET Core で DateOnly 型を使用

by WebSurfer 19. July 2022 13:28

先の記事「PostgreSQL で Movie チュートリアル (CORE)」で書きましたように、Npgsql 6.0 の timestamp with time zone 型と .NET の DateTime 型の扱いの不整合のため例外がスローされるという問題があります。

その記事では臨時処置的に Npgsql を 6.0 より前の動作に戻すオプションを追加して解決しましたが、恒久処置的には .NET の DateTime 型を DateOnly 型に変えて、対応する PostgreSQL の型を date 型にするべきではないかと思いました。

というわけでそれを試してみましたので以下に顛末を書いておきます。ASP.NET Core はまだ DateOnly 型を十分にサポートしてないようで、すんなりとはいかなかったです。ASP.NET Core で DateOnly 型を使うのは時期尚早なのかも。

(1) DateTime 型を DateOnly 型に変更

Movie クラスの ReleaseDateプロパティの DateTime 型を DateOnly 型に変更します。

DateTime 型を DateOnly 型に変更

(2) Add-Migration の実行

ソリューションをリビルドしてから、Visual Studio のパッケージマネージャーコンソールで Add-Migration DateOnly コマンドを実行します。結果、以下の画像の通り DateOnly クラスが Migrations フォルダに自動生成されます。

Add-Migration の実行結果

Movie クラスの ReleaseDate プロパティの DateOnly 型に該当する PostgreSQL の型は date となっています。

なお、Add-Migration DateOnly の DateOnly という名前は任意に指定できます。指定した名前で xxxxx_DateOnly.cs (xxxxx は作成日時) という名前のファイルが作成され、それに DateOnly という名前のクラスが定義されます。

(3) Update-Database の実行

Visual Studio のパッケージマネージャーコンソールで Update-Database コマンドを実行します。

Update-Database の実行結果

上の画像の ReleaseData 列を見てください。Update-Database の実行前は timestamp with time zone 型であったものが date 型に変更されています。

(4) プロジェクトの実行

以上で完了のはずなのですが、プロジェクトを実行して Index 画面を表示すると ReleaseDate の表示が変でした。html ソースは以下のとおりで、DataOnly のプロパティが表示されます。

<div class="display-label">Year</div>
<div class="display-field">2022</div>
<div class="display-label">Month</div>
<div class="display-field">7</div>
<div class="display-label">Day</div>
<div class="display-field">10</div>
<div class="display-label">DayOfWeek</div>
<div class="display-field">Sunday</div>
<div class="display-label">DayOfYear</div>
<div class="display-field">191</div>
<div class="display-label">DayNumber</div>
<div class="display-field">738345</div>

Movie クラスの ReleaseDate プロパティに DisplayFormat 属性を付与してみましたが何も効果はなかったです。

さらに、Create, Edit 操作では、ブラウザからは期待通りに ReleaseDate=2022-07-18 という形で送信されるものの、アクションメソッドの引数には 0001/01/01 が渡される、すなわちモデルバインディングできてないという問題がありました。

ググって調べてみると、DateOnlyTimeOnly.AspNet という NuGet パッケージが見つかりました。Web API 用とのことですが、とりあえず試してみることにしました。

DateOnlyTimeOnly.AspNet

NuGet パッケージをインストールしたら、Program.cs で UseDateOnlyTimeOnlyStringConverters を AddControllers のオプションとして登録します。

DateOnlyTimeOnly.AspNet

結果、Index 画面の ReleaseDate は 2022/07/18 という形で表示されるようになりました。Create, Edit 操作でのモデルバインディングも正しく行われるようになりました。

もちろん、AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); は削除しても例外は出ず、Create, Edit とも成功します。

ホントはこのような追加のパッケージなしでも DateTime 型を使った場合と同様に動くべきだと思うのですが・・・

Tags: , , , ,

CORE

PostgreSQL で Movie チュートリアル (CORE)

by WebSurfer 18. July 2022 18:48

データベースに PostgreSQL を利用して、Micorsoft の ASP.NET Core MVC のチュートリアル「ASP.NET Core MVC の概要」および「パート 4、ASP.NET Core MVC アプリにモデルを追加する」に従ってアプリを作る方法を書きます。

Movie アプリ

先の記事「MySQL で Movie チュートリアル (CORE)」、「SQLite で Movie チュートリアル (CORE)」でデータベースにそれぞれ MySQL, SQLite を使った例を書きましたが、この記事はその PosrgreSQL 版です。

PostgreSQL 本体は先の記事「PostgreSQL をインストールしました」でインストールした v14.4 を使います。

(1) プロジェクトの作成

チュートリアル「ASP.NET Core MVC の概要」の通り、Visual Studio 2022 のテンプレートを利用して対象のフレームワークは .NET 6.0認証「なし」の ASP.NET Core MVC アプリを作成します。

対象のフレームワークを .NET Core 3.1 とか .NET 5.0 にした場合は NuGet パッケージのバージョンの選び方に注意してください。違うとスキャフォールディングでエラーになるかもしれません。

また、認証を「個別のアカウント」にすると SQL Server を利用した Entity Framework 関係のパッケージがインストールされ話がややこしくなりますので、まずは認証は「なし」でやってみることをお勧めします。

(2) モデルの定義とスキャフォールディングの実行

チュートリアル「パート 4、ASP.NET Core MVC アプリにモデルを追加する」に従って、モデル(エンティティ)クラスの定義をプロジェクトに既存の Models フォルダに追加します。

using System.ComponentModel.DataAnnotations;

namespace PosrgreSqlMovie.Models
{
    public class Movie
    {
        public int Id { get; set; }
        public string? Title { get; set; }

        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string? Genre { get; set; }
        public decimal Price { get; set; }
    }
}

次に、Microsoft.EntityFrameworkCore.Design を NuGet からインストールします。この記事では、この記事を書いた時点での最新版 6.0.7 を使いました。対象のフレームワークが .NET Core 3.1 とか .NET 5.0 の場合は、Microsoft.EntityFrameworkCore.Design のバージョンもそれに合わせるのが良さそうです。バージョンが違うとスキャフォールディングでエラーになるかもしれません。

チュートリアルの通り Visual Studio でスキャフォールディングを実行すると以下の操作が自動的に行われます。(コードジェネレータ関係のエラーが出る場合がありますが、その際は下の追加 NuGet パッケージがインストールされていることを確認してから再度スキャフォールディングを行うと成功すると思います)

  1. 他に必要な NuGet パッケージ(Microsoft.EntityFrameworkCore.SqlServer, Microsoft.EntityFrameworkCore.Tools, Microsoft.VisualStudio.Web.CodeGeneration.Design)の追加
  2. Data フォルダにコンテキストクラスの作成
  3. Program.cs ファイル (.NET 5.0 以前の場合は Startup.cs ファイル) でコンテキストクラスの登録
  4. appsettings.json ファイルへの接続文字列の追加
  5. CRUD 操作に必要な Controller / View 一式の生成

SQL Server の場合は上記でプロジェクトは完成ですが、PostgreSQL を利用する場合は上の 1, 3, 4 に以下の変更を行う必要があります。

(3) NuGet パッケージの追加

NuGet パッケージ Npgsql.EntityFrameworkCore.PostgreSQL をインストールします。その結果が以下の画像です。

NuGet パッケージ

スキャフォールディングで自動的に追加される Microsoft.EntiryFrameworkCore.Tools のバージョンがランタイムのバージョンより古い場合は Migration 操作の際警告が出るのと思いますので更新してください。

注意: Microsoft.EntityFrameworkCore.SqlServer はモデルクラスの定義によってはスキャフォールディングで必要になるケースがあるようです。具体的にどのようなケースで必要になるかは調べ切れてませんが、データアノテーション属性が関係しているような感じです。なので、この先モデルの定義を変更して Migration ⇒ スキャフォールディング操作を繰り返すなら、Microsoft.EntityFrameworkCore.SqlServer は残しておいた方が良さそうです。

(4) Program.cs ファイルの修正

スキャフォールディング操作で Program.cs ファイル (.NET 5.0 以前の場合は Startup.cs ファイル) でサービスに自動的にコンテキストクラスが登録されますが、それは SQL Server 用なので、以下のように PostgreSQL 用に変更します。

UseSqlServer を UseNpgsql に変更

コンテキストクラスの登録はコントローラーへの DI に必要です。登録してあれば、フレームワークがクライアントからの要求を受けてコントローラーを初期化する際、コンテキストクラスを初期化してコンストラクタ経由で渡してくれます。

(5) 接続文字列を PostgreSQL 用に変更

スキャフォールディング操作で appsettings.json ファイルに接続文字列が自動生成されますが、それは SQL Server (LocalDB) 用なので PostgreSQL 用に変更します。

接続文字列の変更

データベース名は任意です。上の画像のように Database=Movie というようにデータベース名を指定すると、Entity Framework Code First の機能を使って Movie という名前のデータベースを新たに生成し、そこに必要なテーブルを生成してくれます。

(6) Add-Migration の実行

ソリューションをリビルドしてから、Visual Studio のパッケージマネージャーコンソールで Add-Migration InitialCreate コマンドを実行します。結果、以下の画像の通り InitialCreate クラスが Migrations フォルダに自動生成されます。

Add-Migration の実行結果

Movie クラスの ReleaseDate プロパティ、Price プロパティの型はそれぞれ DateTime、decimal ですが、PostgreSQL にはそれらに該当する型がないらしく、それぞれ timestamp with time zone, numeric となっています。(後述しますが、それにより問題が出ます)

Add-Migration InitialCreate の InitialCreate という名前は任意に指定できます。指定した名前で xxxxx_InitialCreate.cs (xxxxx は作成日時) という名前のファイルが作成され、それに InitialCreate という名前のクラスが定義されます。

(7) Update-Database の実行

Visual Studio のパッケージマネージャーコンソールで Update-Database コマンドを実行します。これにより以下の画像の通り PostgreSQL データベースが生成されます。

Update-Database の実行結果

接続文字列で Database=Movie とした通り Movie という名前でデータベースが生成され、InitialCreate クラスの name: "Movie" で指定された名のテーブルが生成されています。

Movie クラスの ReleaseDate プロパティ、Price プロパティに該当する Movie テーブルのフィールドの型は、上のコードの InitialCreate クラスの指定通りそれぞれ timestamp with time zone, numeric となっています。

(8) プロジェクトの実行

これで完成と思って、プロジェクトを実行し Create 画面を表示してレコードを追加しようとしたら・・・

InvalidCastException: Cannot write DateTime with Kind=Unspecified to PostgreSQL type 'timestamp with time zone', only UTC is supported. Note that it's not possible to mix DateTimes with different Kinds in an array/range. See the Npgsql.EnableLegacyTimestampBehavior AppContext switch to enable legacy behavior."

・・・という例外がスローされて失敗します。(汗)

エラーメッセージでググって調べてヒットした記事「Date and Time Handling」によると "Npgsql 6.0 introduced some important changes to how timestamps are mapped" とのことで変更があったようです。Npgsql 6.0 より前の動作に戻すには以下のコードを追加すると書いてあります。

AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);

ステップ (2) で Data フォルダに自動生成されたコンテキストクラスに上のコードを追加することで、臨時処置的な解決策かもしれませんが、例外はスローされなくなります。

レコードの Create 結果

上の修正を加えた後でプロジェクトを実行して、Create 画面でレコードを 3 件追加し Index 画面でその一覧を表示したのがこの記事の一番上の画像です。

上の例外に対する恒久処置ですが、Movie クラスの ReleaseDateプロパティの DateTime 型を DateOnly 型に変えて、対応する PostgreSQL の型を date 型にするという記事を目にしました。それをやってみた結果を別の記事「ASP.NET Core で DateOnly 型を使用」に書きましたので、興味があれば見てください。

Tags: , , , ,

CORE

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  December 2024  >>
MoTuWeThFrSaSu
2526272829301
2345678
9101112131415
16171819202122
23242526272829
303112345

View posts in large calendar