Functional Programming w/ C# LINQ

C# 8 switch 式の威力はすごい。EntityFramework Core 用の Upsert が実質 3 行 *1 で記述できてしまった *2

public static void Upsert<T>(this DbSet target, IEnumerable<T> source) where T : class, IDbMappable =>
  source
    .GroupJoin(target                    ,
               l      => l.GetKeyObject(),
               r      => r.GetKeyObject(),
               (l, r) => r.FirstOrDefault() switch { // data-driven table operation with side-effect expected
                 null                     => target.Add(l) as T, // Insert
                 var f when ! f.Equals(l) => f.Substitute(l)   , // Update
                 _                        => l                 , // Nop
               })
       .ToList();

同一キーレコードであるものないもの、同一値レコードであるものを GroupJoin() と switch 式を用いることでグループ分けし、データドリブンで次のアクションを Insert / Update / Nop (何もしない) と振り分ける。

ラムダ式を要求する LINQ によるデータ処理であるため switch 式を使うのだが、switch 文 ではなくわざわざ switch「式」を使っているにも関わらず、副作用に依拠してテーブル処理しており、逆に出力 ToList() は何も利用していないのがトリッキーである。switch 式がすばらしいのは、変数宣言式が使えない現行 C# にあって、var f として型推論・変数代入で受けられるところ。これにより手続や条件分岐を冗長に記述することなく Equals ()や Substitute() *3 といった式にかけられる。

LINQ を使っていると、データ変換の一連の式の内部で途中結果を複数回参照する場面が多々あり、

var obj    = new { Text = "Sample", Value = 1 };
var result = (var x = obj.Value + 1) * x;

のように、式の流れを止めずに変数宣言式を用いて中間変数を定義したいところだが、これができない。今後は、代わりに

var result = (obj.Value + 1) switch { var x => x * x };

と中間変数を受けるだけの目的で switch 式を使うようになるかもしれない。

*1:Upsert の本質は (l, r) ~ f.Substitute(l) の 3 行。

*2:IDbMappable はデータベースにマップする Record が具備すべきインターフェースとして定義しており、主キーとなるオブジェクトを返す GetKeyObject() メソッドをデフォルト実装している。

*3:Substitute() は Record の全プロパティ値を引数のそれに置き換えるメソッドとして定義している。= で代入すると変数の中身ではなく参照が置換されてしまうため。