LoginSignup
1
0

More than 1 year has passed since last update.

.Net6 DateOnly型でテーブルをつくる

Last updated at Posted at 2022-01-18

2年ぶりの記事を書いています。
コロナ禍で環境が大幅に変わったりと色々大変でして, 書く時間がなくて...

 はじめに

最近はMicrosoftがすごいと噂を聞きまして,
自分の新サービスをBlazorで書いてみようかとおもってはじめたのですが
結構苦労したのでここに自分用のメモとして残しておきます.

環境は

C#
.Net6
EFCore6
Blazor
SQLServer

事象

EFCore6 から DateOnly 型が導入されたようなのですが

/Shared/Models/Building.cs
using System.ComponentModel.DataAnnotations;

namespace BlazorApp.Shared.Models
{
    public class Building
    {
        public DateOnly BuildedDate { get; set; }
    }
}

上記のモデルで、migrateを実行すると

dotnet ef migrations add Building

エラーになって、Migrationファイルが作成されません。

The property 'Building.BuildedDate' could not be mapped because it is of type 'DateOnly',which is not a supported primitive type or a valid entity type. 
Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.
Ignore' in 'OnModelCreating'.

[日本語訳]
プロパティ 'Building.BuildedDate'は、サポートされているプリミティブ型または有効なエンティティ型ではないタイプ 'DateOnly'であるため、マップできませんでした。このプロパティを明示的にマップするか、「[NotMapped]」属性を使用するか、「OnModelCreating」の「EntityTypeBuilder.Ignore」を使用して無視します。

このエラーに従ってると目的が達成できなくなりますね....

ひとまずDateTime型でMigrationファイルをつくる

/Shared/Models/Building.cs
- public DateOnly BuildedDate { get; set; }
+ public DateTime BuildedDate { get; set; }
dotnet ef migrations add Building

上記を実行すると

/Server/Migrations/2022XXXXX_Building.cs
namespace BlazorApp.Server.Migrations
{
    public partial class InitialTables : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Buildings",
                columns: table => new
                {
                    BuildingDate = table.Column<DateTime>(type: "datetime2", nullable: false),

   以下略

次に, DatetimeをDateOnlyに変更

/Server/Migrations/2022XXXXX_Building.cs
- BuildingDate = table.Column<DateTime>(type: "datetime2", nullable: false),
+ BuildingDate = table.Column<DateOnly>(type: "date", nullable: false),
/Shared/Models/Building.cs
- public DateTime BuildedDate { get; set; }
+ public DateOnly BuildedDate { get; set; }

この段階でMigrateしても上でみたおなじエラーがでます。
そこで、以下のファイルを修正します

/Server/Data/AppDbContext.cs
using BlazorApp.Shared.Models;
+ using Microsoft.EntityFrameworkCore;
+ using Microsoft.EntityFrameworkCore.ChangeTracking;
+ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace BlazorApp.Server.Data
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions options) : base(options)
        {
        }
+
+        protected override void ConfigureConventions(ModelConfigurationBuilder builder)
+        {
+            builder.Properties<DateOnly>()
+                .HaveConversion<DateOnlyConverter>()
+                .HaveColumnType("date");
+            builder.Properties<DateOnly?>()
+                .HaveConversion<NullableDateOnlyConverter,NullableDateOnlyComparer>()
+                .HaveColumnType("date");
+       }

        public DbSet<Building> Buildings => Set<Building>();
    }
+
+    /// <summary>
+    /// Converts <see cref="DateOnly" /> to <see cref="DateTime"/> and vice versa.
+    /// </summary>
+    public class DateOnlyConverter : ValueConverter<DateOnly, DateTime>
+    {
+        /// <summary>
+        /// Creates a new instance of this converter.
+        /// </summary>
+        public DateOnlyConverter() : base(
+                d => d.ToDateTime(TimeOnly.MinValue),
+                d => DateOnly.FromDateTime(d))
+        { }
+    }
+
+    /// <summary>
+    /// Compares <see cref="DateOnly" />.
+    /// </summary>
+    public class DateOnlyComparer : ValueComparer<DateOnly>
+    {
+        /// <summary>
+        /// Creates a new instance of this converter.
+        /// </summary>
+        public DateOnlyComparer() : base(
+            (d1, d2) => d1 == d2 && d1.DayNumber == d2.DayNumber,
+            d => d.GetHashCode())
+        {
+        }
+    }
+
+    /// <summary>
+    /// Converts <see cref="DateOnly?" /> to <see cref="DateTime?"/> and vice versa.
+    /// </summary>
+    public class NullableDateOnlyConverter : ValueConverter<DateOnly?, DateTime?>
+    {
+        /// <summary>
+        /// Creates a new instance of this converter.
+        /// </summary>
+        public NullableDateOnlyConverter() : base(
+            d => d == null
+                ? null
+                : new DateTime?(d.Value.ToDateTime(TimeOnly.MinValue)),
+            d => d == null
+                ? null
+                : new DateOnly?(DateOnly.FromDateTime(d.Value)))
+        { }
+    }
+
+    /// <summary>
+    /// Compares <see cref="DateOnly?" />.
+    /// </summary>
+    public class NullableDateOnlyComparer : ValueComparer<DateOnly?>
+    {
+        /// <summary>
+        /// Creates a new instance of this converter.
+        /// </summary>
+        public NullableDateOnlyComparer() : base(
+            (d1, d2) => d1 == d2 && d1.GetValueOrDefault().DayNumber == d2.GetValueOrDefault().DayNumber,
+            d => d.GetHashCode())
+        {
+        }
+    }
}

こちらを参照しました

他にも、以下のやり方があるみたいです。

Install-Package TinyHelpers.EntityFrameworkCore

このあと

dotnet ef migrations add AlterDateOnly
dotnet ef database update

これで, カラムがDateOnly型に変更できました
以下のファイルが追加されます

Service/Migrations/2022XXX_AlterDateOnly.cs
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace BlazorApp.Server.Migrations
{
    public partial class AlterDateOnly : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AlterColumn<DateTime>(
                name: "BuildedDate",
                table: "Buildings",
                type: "date",
                nullable: false,
                oldClrType: typeof(DateTime),
                oldType: "datetime2");
        }
    }
}

うまくいってるんだけど....

以下のファイルをのぞいてみると

Server/Migrations/AppDbContextModelSnapshot.cs
using BlazorApp.Server.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace BlazorApp.Server.Migrations
{
    [DbContext(typeof(AppDbContext))]
    partial class AppDbContextModelSnapshot : ModelSnapshot
    {
        protected override void BuildModel(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity("ChintaiApp.Shared.Models.Building", b =>
                {
                    b.Property<DateTime>("BuildedDate")
                        .HasColumnType("date");
以下略

DateTime のままなんですよね...
これでうまくいってるけど、なぜなんでしょうね。。。

最後に

日本語でこれに関する記事が見当たらなかったので, 記事を書きました.
Date型すら標準機能で作れない.Netってフレームワークの完成度が低い気がして....
DateOnly型がないときは, どうやって開発してたんですかね?
不思議だ

次は Seedについて書きます。

1
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0