EntityFrameworkのデータファーストでモデルクラスをINotifyPropertyChangedに対応させる


EntityFrameworkサイコー

EFのデータファーストが最高なので、INotifyPropertyChangedに対応したらもっとサイコーになるよね。
的な。

INotifyPropertyChanged の実装超めんどい

なのでハイパー最高なNuGetのPropertyChanged.Fodyを使う。
INotifyPropertyChangedを自動実装してくれるPropertyChanged.fodyが超便利 - Azureはじめました

上の記事はPropertyChanged.Fodyのかなり古いバージョンなので今この通りに実装するとそんな古い形式使ってんじゃねーよばーかばーか的なエラーになるので注意。

NotifyPropertyChanged.Fodyの実装方法


public class HogeModel{
    public string Name{get;set;}
    public int AnyNumber{get;set;}
}

これを

using System.ComponentModel;

public class HogeModel{
    public event PropertyChangedEventHandler PropertyChanged;

    public string Name{get;set;}
    public int AnyNumber{get;set;}
}

こうする。

後はプロジェクトにFodyWeavers.xmlというファイルを追加して

FodyWeavers.xml
<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
  <PropertyChanged/>
</Weavers>

こうするだけ。超便利。

INotifyPr(略) をedmx で出力しているクラスにどう実装するか

edmxを構成するモデル出力用のT4テンプレートを直接編集すればOK

PropertyChanged.Fodyで追加する記述は

  1. usingにSystem.ComponentModelを追加
  2. class定義にINotifyPropertyChangedを追加
  3. メンバに PropertyChangedEventHandlerを追加

なのでそれぞれ追記する。

1. usingにSystem.ComponentModelを追加

usingを出力しているのは codeStringGenerator.UsingDirectives()なのでそこに追記

public string UsingDirectives(bool inHeader, bool includeCollections = true)
{
    return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion())
        ? string.Format(
            CultureInfo.InvariantCulture,
            "{0}using System;{1}" +
            "{2}",
            inHeader ? Environment.NewLine : "",
            (includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "")
                +Environment.NewLine +"using System.ComponentModel;", //これ
            inHeader ? "" : Environment.NewLine)
        : "";
}

2. class定義にINotifyPropertyChangedを追加

class定義を出力してるのはcodeStringGenerator.EntityClassOpening()

public string EntityClassOpening(EntityType entity)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1}partial class {2}{3}",
        Accessibility.ForType(entity),
        _code.SpaceAfter(_code.AbstractOption(entity)),
        _code.Escape(entity),
        ":INotifyPropertyChanged"); //これ
        //_code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)));
}

(entity.BaseType使わないので横着した。使う場合は適宜修正を。)

3. メンバに PropertyChangedEventHandlerを追加

テンプレートの先頭の方にあるEntityClassOpening(entity)の後に直接記述

<#=codeStringGenerator.UsingDirectives(inHeader: false)#>
<#=codeStringGenerator.EntityClassOpening(entity)#>
{
    #pragma warning disable 0067 
    public event PropertyChangedEventHandler PropertyChanged; //これ
    #pragma warning restore 0067 

ただPropertyChangedEventHandlerを追加しただけだと CS0067(未使用のメンバ)が表示されて大変鬱陶しいのでpragma warning disable - restoreを使って警告表示を抑制してみた。

[C#]コンパイル時の警告を抑制する | anopara

あとはファイルを保存してコンパイルすればOK。
こんな感じに出力される。

//------------------------------------------------------------------------------
// <auto-generated>
//     このコードはテンプレートから生成されました。
//
//     このファイルを手動で変更すると、アプリケーションで予期しない動作が発生する可能性があります。
//     このファイルに対する手動の変更は、コードが再生成されると上書きされます。
// </auto-generated>
//------------------------------------------------------------------------------

namespace models
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;

    public partial class 銀行マスタ:INotifyPropertyChanged
    {
            public event PropertyChangedEventHandler PropertyChanged;
            [Key]
            public string 銀行コード { get; set; }
            public string 銀行名 { get; set; }
            public string 銀行カナ { get; set; }
    }
}

いいね!

使いみちとか

INotifyPropertyChanged吐けばWinFormsのDataBindingとかWPFのBindingとかが勝手にリセットしてくれるので、ViewModel的に使ったりマジなんでもありでサイコー。