PostgreSQL EF CoreでのJsonの処理

EntityFrameworkCore の7.0プレビューが出たので、PostgreSQL EF Coreでの、Jsonの処理について検証しました。

# 環境
- Microsoft.EntityFrameworkCore 7.0.0-preview.1.22076.6
- Npgsql 7.0.0-preview.1
- Npgsql.EntityFrameworkCore.PostgreSQL 7.0.0-preview.1
- .NET 6

Npgsql.Json.NET 7.0.0-preview.1  は必要なさそうです。

# 前提
https://www.npgsql.org/efcore/mapping/json.html

PostgresSQLの、Entiry Framework CoreでのJSONの取り扱いは、

- **A) String mapping**
- **B) POCO mapping**
- **C) JsonDocument DOM mapping**

の3種類
Aの文字列で処理するのは、自分でJSONへのシリアル化と逆シリアル化（パース）をする。他の２つに比べて、制限された方法である、と解説されています。

# メリット・デメリット
以下、AとBとを実際に実装してみて得られた知見。

## Aの方法
- **自分でシリアル化・逆シリアル化が必要。** タイミングとして、EFで、ContextをLoadする時に逆シリアル化し、Saveする前にシリアル化する処理、でOK。（他の処理のタイミングとして、プロパティをgetした時、setした時でも、出来そうだ）
- このままで context.SaveChangesが機能する。
- `double.NaN, double.PositiveInfinity, double.NegativeInfinity`　は、 `System.Text.Json(Microsoft)`を利用してJsonとして処理できる。
しかし、Brushなど、`System.Text.Json`でも処理できない型があるので、それは文字列にするなどの処理が必要。

## Bの方法
- **自分でJsonのシリアライズ・逆シリアル化は不要。**
- ただし、プロパティの値を変更しても、このままでは`context.SaveChanges`が機能しない。なんらかの処理が必要。
    - 例1　Jsonのプロパティを変更したと示す、`IsModified` をtrueにする。
    `context.Entry(groupB).Property(e => e.CustomerJsonb).IsModified = true;`
    - 例2　Change Tracker を機能させるために、`ValueComparer`を利用する。
- `dobule.NaN`などは、このままでは処理できない（現時点では）。文字列として処理すればOK。

# Jsonで処理出来ない型
AもBもJsonとして処理できない型は、文字列にしたりして保存すると良い。
例：Brush

# doubleの無限大・非数をJson用に文字列にする例
- Bの方法で利用します。Aの方法で、`System.Text.Json(Microsoft)`を利用するなら、無限大・非数をJsonにできるので、必要ないです。
- テストした限りではこれで機能すると思います。
- **アプリ内部では、`Number`を利用。Jsonには、`NumberS`を利用。**
- （string?とかの?は、C# 8.0以降の、Null 許容参照型を示します）
``` csharp
    private double _number;
    [JsonIgnore]
    public double Number
    {
        get { return _number; }
        set
        {
            _number = value;
            _numberS = value.ToString();
        }
    }

    private string? _numberS;
    public string? NumberS
    {
        get { return _numberS; }
        set
        {
            _numberS = value;
            _number = double.Parse(value);
        }
    }
```

# BrushをJson用に文字列にする例
- **アプリ内部では、Colorプロパティを利用。Jsonには、ColorSを利用。**
- `SolidColorBrush`のみ対象にしています。
``` csharp
    private Brush? _color;
    [JsonIgnore]
    public Brush? Color
    {
        get { return _color; }
        set
        {
            _color = value;
            _colorS = value?.ToString(); // →　value is null ? null : value.ToString();
        }
    }

    private string? _colorS;
    public string? ColorS
    {
        get { return _colorS; }
        set
        {
            _colorS = value;
            _color = Utils.GetBrushFromString(value);
        }
    }
```

# Bの方法でデーターベースに保存されたJsonの例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132059/6b9cfcbd-ef18-2925-e6e2-618ff0fb9c91.png)


PostgreSQLの、`jsonb` と、`json` とは少し違います。ただ、 **「\u221E」** は、**「∞」** なので、この点は単に表示の違いだと思います。

# Bの方法のEntity全体

``` csharp
public class GroupB
{
    public int Id { get; set; }
    [Column(TypeName = "jsonb")]
    public CustomerB? CustomerJsonb { get; set; }  //Jsonbで保存
    [Column(TypeName = "json")]
    public CustomerB? CustomerJson { get; set; }  //Jsonで保存
}
public class CustomerB
{
    public string? Name { get; set; }

    private double _number;
    [JsonIgnore]
    public double Number
    {
        get { return _number; }
        set
        {
            _number = value;
            _numberS = value.ToString();
        }
    }

    private string? _numberS;
    public string? NumberS
    {
        get { return _numberS; }
        set
        {
            _numberS = value;
            _number = double.Parse(value);
        }
    }

    private Brush? _color;
    [JsonIgnore]
    public Brush? Color
    {
        get { return _color; }
        set
        {
            _color = value;
            _colorS = value?.ToString(); // →　value is null ? null : value.ToString();
        }
    }

    private string? _colorS;
    public string? ColorS
    {
        get { return _colorS; }
        set
        {
            _colorS = value;
            _color = Utils.GetBrushFromString(value);
        }
    }
}

public static class Utils
{
    public static SolidColorBrush? GetBrushFromString(string? brushString)
    {
        return string.IsNullOrEmpty(brushString) ? null : (SolidColorBrush?)new BrushConverter().ConvertFromString(brushString);
    }
}
```

# 備考
- 今まで設定の保存に、XMLを利用していたのですが、Jsonの方が扱いやすいです。
- double、BrushのJsonシリアル化・逆シリアル化を、改めて考えてみて整理しました。今まで、煩雑な処理をしていたのを、改善できました。
- こうして記事にまとめておかないと、自分でも忘れてしまう。
	

# 参考
- [Entity framework not detecting jsonb properties changes in c#](https://stackoverflow.com/questions/62021228/entity-framework-not-detecting-jsonb-properties-changes-in-c-sharp)
- [How to store JSON in an entity field with EF Core?](https://stackoverflow.com/questions/44829824/how-to-store-json-in-an-entity-field-with-ef-core/71189912)
- [PostgreSQLにおけるjson/jsonb型の取り扱い](https://rinoguchi.net/2021/03/postgresql-json.html)
- [EF7でのJSONサポートについて](https://github.com/dotnet/efcore/issues/4021#issuecomment-1033896410)
- [How to serialize a Windows.Media.Brush](https://stackoverflow.com/a/29514640/9924249)