FTPにCSVを始めとしたテキストベースのファイルをアップロード・ダウンロードした場合にファイルの文字コードが変わってしまう事があります。 その原因と対処法を紹介したいと思います。 改行コードが変わる…
ドメイン駆動設計(DDD)のValueObject(値オブジェクト)をEntityFramework Core(EFCore)で自動的にマッピングする方法を紹介します。
EFCore2.0以降では、所有エンティティ型としてValueObjectを永続化する機能が提供されています。
マッピングだけではなく、ValueObjectとして定義したEntityからマイグレーションを生成することも可能です。
EntityとValueObjectを定義する
注文Orderと配送先住所Addressの場合の各クラスを以下のように定義します。
配送先住所に含まれる郵便番号、都道府県、住所等をValueObjectとして定義しています。
クラス図
Order (Entity)
using System; using System.Collections.Generic; using System.Linq; namespace OrderDomain.Models.OrderAggregate { public class Order { protected Order() { } public Order(DateTime orderDate, Address address) { if (address == null) throw new ArgumentNullException(nameof(address)); OrderDate = orderDate; Address = address; } public DateTime OrderDate { get; private set; } public Address Address { get; private set; } } }
Address (ValueObject)
using System; using System.Collections.Generic; namespace OrderDomain.Models.OrderAggregate { public class Address { public Address() { } public Address(string postalCode, int prefectureId, string address1, string address2) { if (postalCode.Length > 7) throw new ArgumentOutOfRangeException(nameof(postalCode)); if (address1 == null) throw new ArgumentNullException(nameof(address1)); if (address1.Length > 50) throw new ArgumentOutOfRangeException(nameof(address1)); if (address2 == null) throw new ArgumentNullException(nameof(address2)); if (address2.Length > 50) throw new ArgumentOutOfRangeException(nameof(address2)); PostalCode = postalCode; PrefectureId = prefectureId; Address1 = address1; Address2 = address2; } public string PostalCode { get; private set; } public string PrefectureId { get; private set; } public string Address1 { get; private set; } public string Address2 { get; private set; } } }
EntityTypeConfigurationを作成する
IEntityTypeConfigurationインターフェースを実装したConfigurationクラスを定義します。
・AddressプロパティからWithOwnerメソッドを引数なしで実行してAddress自身は所有権の関係にないことを明示する
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using OrderDomain.Models.OrderAggregate; namespace Infrastructure.EntityConfigurations { public class OrderConfiguration : IEntityTypeConfiguration<Order> { public void Configure(EntityTypeBuilder<Order> builder) { builder.HasKey(o => o.Id); builder.OwnsOne(o => o.Address, a => a.WithOwner()); } } }
DbContextのOnModelCreatingに読み込み定義
EF CoreのDbContextのOnModelCreatingメソッドをオーバーライドして、先に作成したEntityConfigurationを読み込み、反映する様に定義します。
using Infrastructure.EntityConfigurations; using Microsoft.EntityFrameworkCore; using OrderDomain.Models.OrderAggregate; namespace Infrastructure { public class EcDbContext : DbContext { public EcDbContext(DbContextOptions<EcDbContext> options) : base(options) { } public DbSet<Order> Orders { get; set; } protected override void OnModelCreating(ModelBuilder builder) { builder.ApplyConfiguration(new OrderConfiguration()); } } }
マイグレーションを生成する
あとは、通常通りdotnet ef migrations...
からマイグレーションファイルを生成します。
Address(ValueObject)はorderテーブル内に追加され、各カラムのプレフィックスにValueObjectクラス名のAddressが追加されます。
protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "orders", schema: "order", columns: table => new { Id = table.Column<int>(nullable: false) .Annotation("Sqlite:Autoincrement", true), OrderDate = table.Column<DateTime>(nullable: false), Address_PostalCode = table.Column<string>(nullable: true), Address_Prefecture = table.Column<int>(nullable: true), Address_Address1 = table.Column<string>(nullable: true), Address_Address2 = table.Column<string>(nullable: true) }, constraints: table => { table.PrimaryKey("PK_orders", x => x.Id); }); }
取得すると自動的にValueObjectがマッピングされる
EF Coreからテーブルに永続化されているOrderエンティティを取得します。
Orderに含むAddressクラスがマッピングされた状態で取得できることが確認できます。
using (var context = new EcDbContext()) { var order = context.Order.First(); Console.WriteLine(order.Address); }
コメントを書く