プログラムを中心とした個人的なメモ用のブログです。 タイトルは迷走中。
内容の保証はできませんのであしからずご了承ください。

2019/04/02

EF Core で SQLite に配列を格納する

event_note2019/04/02 5:27

DB に配列を格納することの是非はさておき、EntityFramework Core を使って SQLite に配列を格納する方法です。

環境

  • Visual Studio 2017
  • .NET Core 2.2

モデル

イメージとして、以下のような int の配列を持つオブジェクトを DB に格納したいとします。

public class PersonDbContext : DbContext
{
    public DbSet<Person> Persons { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Data Source=sqlitetest.db");
    }
}

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int[] Attributes { get; set; }
}

しかし、このままビルドすると以下のようなエラーが表示されます。

System.InvalidOperationException: 'The property 'Person.Attributes' could not be mapped, because it is of type 'Int32[]' 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'.'

配列を保存するためには

DB には配列を文字列として格納し、取り出すときに分割して int に変換するようにします。

先ほどのコードを以下のように変更します。

public class PersonDbContext : DbContext
{
    public DbSet<Person> Persons { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Data Source=sqlitetest.db");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>()
            .Property<string>("AttributeCollection")
            .HasField("_attributes");
    }
}

public class Person
{
    private static readonly char delimiter = ';';
    public int Id { get; set; }
    public string Name { get; set; }
    string _attributes;
    [NotMapped]
    public int[] Attributes
    {
        get => _attributes.Split(delimiter).Select(x => int.Parse(x)).ToArray();
        set => _attributes = string.Join(delimiter, value);
    }
}

int[] Attributes には NotMapped 属性を付与することで、O/R マッパーの対象から外します。
その代わりに string _attributes というフィールドを用意し、int[] Attributes のプロパティで、_attributes に対する getset を用意します。
int 配列と文字列の変換には、デリミターとして ; を使用しています。

DbContext クラスの OnModelCreating メソッドでは、_attributes フィールドに AttributeCollection というプロパティを割り当てるように設定しています。

以上で、Person クラスを使う側からは DB を意識せずに配列を扱うことができます。

using (var db = new PersonDbContext())
{
    var person = new Person
    {
        Name = name,
        Attributes = new int[] { 0, 1, 2 }
    };
    db.Persons.Add(person);
    db.SaveChanges();
}
using (var db = new PersonDbContext())
{
    foreach (var person in db.Persons)
    {
        Console.WriteLine($"ID = {person.Id}, Name = {person.Name}, Attributes={string.Join(", ", person.Attributes)}");
    }
}

DB Browser for SQLite で中身を見ると、文字列として格納されているのがわかります。