エンティティフレームワーク-コードファースト-List <String>を格納できません


106

私はそのようなクラスを書きました:

class Test
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    [Required]
    public List<String> Strings { get; set; }

    public Test()
    {
        Strings = new List<string>
        {
            "test",
            "test2",
            "test3",
            "test4"
        };
    }
}

そして

internal class DataContext : DbContext
{
    public DbSet<Test> Tests { get; set; }
}

コードを実行した後:

var db = new DataContext();
db.Tests.Add(new Test());
db.SaveChanges();

私のデータは保存されますが、ちょうどなっていますId文字列リストに適用されるテーブルや関係はありません。

何が悪いのですか?文字列 も作ってみましたvirtualが何も変わりませんでした。

ご協力ありがとうございました。


3
List <sting>がどのようにデータベースに保存されると思いますか?それはうまくいきません。文字列に変更します。
Wiktor Zychla 2013

4
リストがある場合、それはあるエンティティを指している必要があります。EFがリストを保存するには、2番目のテーブルが必要です。2番目のテーブルでは、リストのすべてを配置し、外部キーを使用してTestエンティティをポイントします。したがって、IdpropertyとMyStringproperty を持つ新しいエンティティを作成し、次にそのリストを作成します。
Daniel Gabriel

1
そうです...データベースに直接保存することはできませんが、Entity Frameworkがそれ自体で新しいエンティティを作成することを望んでいました。コメントしてくださってありがとうございます。
Paul

回答:


161

Entity Frameworkは、プリミティブ型のコレクションをサポートしていません。エンティティ(別のテーブルに保存されます)を作成するか、文字列処理を行ってリストを文字列として保存し、エンティティが実体化された後にリストに入力することができます。


エンティティにエンティティのリストが含まれている場合はどうなりますか?マッピングはどのように保存されますか?
A_Arnold

依存-別のテーブルにある可能性が高い。
Pawel

json形式のテキストをシリアル化してから圧縮して保存するか、必要に応じて暗号化して保存することができます。どちらの方法でも、フレームワークに複合型テーブルマッピングを実行させることはできません。
Niklas

89

EF Core 2.1以降:

プロパティ:

public string[] Strings { get; set; }

OnModelCreating:

modelBuilder.Entity<YourEntity>()
            .Property(e => e.Strings)
            .HasConversion(
                v => string.Join(',', v),
                v => v.Split(',', StringSplitOptions.RemoveEmptyEntries));

5
EF Coreの優れたソリューション。文字列変換への聖霊降臨祭の問題があるようですが。私はそれをそのように実装する必要がありました:.HasConversion(v => string.Join( ";"、v)、v => v.Split(new char [] {';'}、StringSplitOptions.RemoveEmptyEntries));
Peter Koller

8
これが唯一の正解です。他のすべてのものはモデルを変更する必要があり、ドメインモデルは永続性を無視するべきであるという原則に違反します。(パーシスタンスモデルとドメインモデルを別々に使用している場合は問題ありませんが、実際にそうする人はほとんどいません。)
Marcell Toth

2
string.Joinの最初の引数としてcharを使用することはできず、StringSplitOptionsも提供する場合は、string.Splitの最初の引数としてchar []を提供する必要があるため、私の編集要求を受け入れる必要があります。
ドミニク

2
.NET Coreではできます。私は自分のプロジェクトの1つでこの正確なコードを使用しています。
Sasan

2
.NET Standardでは使用できません
Sasan

54

この回答は、@ Sasanおよび@CAD blokeによって提供されたものに基づいています。

EF Core 2.1+でのみ動作します(.NET Standard互換ではありません)(Newtonsoft JsonConvert

builder.Entity<YourEntity>().Property(p => p.Strings)
    .HasConversion(
        v => JsonConvert.SerializeObject(v),
        v => JsonConvert.DeserializeObject<List<string>>(v));

EF Core Fluent構成を使用して、ListJSONとの間でシリアル化/非シリアル化します。

なぜこのコードが、あなたが努力することができるすべての完璧な組み合わせなのか:

  • Sasnの元の答えの問題は、リスト内の文字列にカンマ(または区切り文字として選択された任意の文字)が含まれている場合、単一のエントリが複数のエントリに変わるが、読みやすく、最も簡潔。
  • CAD blokeの答えの問題は、醜く、モデルを変更する必要があるということです。これは悪い設計手法です(Sasanの答えに関するMarcell Tothのコメントを参照)。しかし、それはデータセーフな唯一の答えです。

7
ブラボー、これはおそらく受け入れられる答えになるはずです
Shirkan

1
これが.NET FrameworkとEF 6で機能することを願っています。これは本当にエレガントなソリューションです。
CADは19

これは素晴らしいソリューションです。ありがとう
Marlon

そのフィールドでクエリを実行できますか?私の試みは惨めに失敗しました:var result = await context.MyTable.Where(x => x.Strings.Contains("findme")).ToListAsync();何も見つかりません。
Nicola Iarocci

3
私の質問に答えるには、ドキュメントを引用します。「値の変換を使用すると、EF Coreが式をSQLに変換する機能に影響が及ぶ可能性があります。そのような場合は警告がログに記録されます。これらの制限の削除は、将来のリリースで検討される予定です。」-それでもいいのに。
Nicola Iarocci

44

私はこれが古い質問であることを知っており、Pawelが正しい答えを出しました。文字列処理を行う方法のコード例を示し、プリミティブ型のリストの余分なクラスを避けたかっただけです。

public class Test
{
    public Test()
    {
        _strings = new List<string>
        {
            "test",
            "test2",
            "test3",
            "test4"
        };
    }

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    private List<String> _strings { get; set; }

    public List<string> Strings
    {
        get { return _strings; }
        set { _strings = value; }
    }

    [Required]
    public string StringsAsString
    {
        get { return String.Join(',', _strings); }
        set { _strings = value.Split(',').ToList(); }
    }
}

1
パブリックプロパティを使用する代わりに静的メソッドを使用しないのはなぜですか?(または、プロシージャプログラミングのバイアスを示していますか?)
Duston

@randomsなぜ2つのリストを定義する必要があるのですか?1つはプロパティとして、もう1つは実際のリストとして?ここでのバインディングがどのように機能するかについても説明していただければ幸いです。このソリューションは私にはうまく機能せず、ここでバインディングを理解できません。ありがとう
LiranBo 2015年

2
プライベートリストが1つあり、2つのパブリックプロパティが関連付けられています。アプリケーションで文字列を追加および削除するために使用するStringsと、コンマ区切りのリストとしてデータベースに保存される値であるStringsAsStringです。あなたが何を求めているのか本当にわかりませんが、バインディングはプライベートリスト_stringsであり、2つのパブリックプロパティを接続します。
ランダム、2015年

1
この回答は,文字列で(カンマ)エスケープしないことに注意してください。リスト内の文字列に1つ以上の,(カンマ)が含まれている場合、その文字列は複数の文字列に分割されます。
Jogge 2017

2
string.Joinコンマ(文字列の)二重引用符ではなく、(charの)単一引用符で囲む必要があります。msdn.microsoft.com/en-us/library/57a79xd0(v=vs.110).aspxを
Michael Brandon Morris

29

JSON.NETが助けになります。

これをJSONにシリアル化してデータベースに永続化し、非シリアル化して.NETコレクションを再構成します。これは、Entity Framework 6とSQLiteで期待したよりもパフォーマンスが良いようです。私はあなたが求めてList<string>いたのを知っていますが、これはうまく機能するさらに複雑なコレクションの例です。

永続化プロパティにタグを付けた[Obsolete]ので、通常のコーディングの過程で「これはあなたが探しているプロパティではない」ことが非常に明白になります。「本物の」プロパティにはタグが付けられている[NotMapped]ため、エンティティフレームワークはそれを無視します。

(無関係な接線):もっと複雑な型でも同じことができますが、オブジェクトのプロパティのクエリを自分で行うのが難しいと思ったことはありませんか。(はい、私の場合)。

using Newtonsoft.Json;
....
[NotMapped]
public Dictionary<string, string> MetaData { get; set; } = new Dictionary<string, string>();

/// <summary> <see cref="MetaData"/> for database persistence. </summary>
[Obsolete("Only for Persistence by EntityFramework")]
public string MetaDataJsonForDb
{
    get
    {
        return MetaData == null || !MetaData.Any()
                   ? null
                   : JsonConvert.SerializeObject(MetaData);
    }

    set
    {
        if (string.IsNullOrWhiteSpace(value))
           MetaData.Clear();
        else
           MetaData = JsonConvert.DeserializeObject<Dictionary<string, string>>(value);
    }
}

私はこの解決策を非常に醜く見つけましたが、実際にはそれが唯一の正気な解決策です。任意の文字を使用してリストに参加し、それを分割することを提供するすべてのオプションは、分割文字が文字列に含まれている場合、野生の混乱に変わる可能性があります。Jsonはもっと正気であるはずです。
Mathieu VIALES

1
私が作ってしまった答え、この1、もう一方の強力なポイントを使用して各回答の問題(醜/データの安全性を)修正するもう一つの「マージ」です。
Mathieu VIALES

13

単純化するために-

エンティティフレームワークはプリミティブをサポートしていません。クラスを作成してラップするか、別のプロパティを追加してリストを文字列としてフォーマットします。

public ICollection<string> List { get; set; }
public string ListString
{
    get { return string.Join(",", List); }
    set { List = value.Split(',').ToList(); }
}

1
これは、リストアイテムに文字列を含めることができない場合です。それ以外の場合は、エスケープする必要があります。または、より複雑な状況でリストをシリアライズ/デシリアライズします。
Adam Tal

3
また、ICollectionプロパティで[NotMapped]を使用することを忘れないでください
Ben Petersen

7

もちろん、パヴェルは正しい答えを出しました。しかし、この投稿で私はEF 6以降でプライベートプロパティを保存できることを発見しました。文字列を間違った方法で保存することができないので、私はこのコードを好みます。

public class Test
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Column]
    [Required]
    private String StringsAsStrings { get; set; }

    public List<String> Strings
    {
        get { return StringsAsStrings.Split(',').ToList(); }
        set
        {
            StringsAsStrings = String.Join(",", value);
        }
    }
    public Test()
    {
        Strings = new List<string>
        {
            "test",
            "test2",
            "test3",
            "test4"
        };
    }
}

6
文字列にカンマが含まれている場合はどうなりますか?
チョーキー2017年

4
この方法で行うことはお勧めしません。参照が変更StringsAsStringsされた場合にのみ更新され、例で発生するのは割り当て時のみです。割り当て後にアイテムをリストに追加またはリストから削除しても、バッキング変数更新されません。これを実装する適切な方法は、他の方法ではなく、リストのビューとして公開することです。プロパティのアクセサーで値を結合し、それらをアクセサーで分割します。Strings StringsStringsAsStringsStringsAsStringsStringsgetStringsAsStringsset
jduncanator 2017年

プライベートプロパティの追加を回避するには(副作用はありません)、シリアル化されたプロパティのセッターをプライベートにします。jduncanatorはもちろん正しいです。リスト操作をキャッチしない場合(ObservableCollectionを使用しますか?)、EFは変更を認識しません。
レオニダス

@jduncanatorが言及したように、リストに変更が加えられた場合(たとえば、MVVMでバインド)、このソリューションは機能しません
Ihab Hajj

7

@Mathieu Viales回答を少し調整します。新しいSystem.Text.Jsonシリアライザを使用した.NET標準互換のスニペットをここに示します。これにより、Newtonsoft.Jsonへの依存がなくなります。

using System.Text.Json;

builder.Entity<YourEntity>().Property(p => p.Strings)
    .HasConversion(
        v => JsonSerializer.Serialize(v, default),
        v => JsonSerializer.Deserialize<List<string>>(v, default));

両方の第二引数ながらことを注意Serialize()し、Deserialize()一般的に任意であり、あなたがエラーを取得します:

式ツリーには、オプションの引数を使用する呼び出しまたは呼び出しを含めることはできません

それぞれを明示的にデフォルト(null)に設定すると、それがクリアされます。


3

ScalarCollection配列を制限し、いくつかの操作オプションを提供するこのコンテナーを使用できます(Gist)。

使用法:

public class Person
{
    public int Id { get; set; }
    //will be stored in database as single string.
    public SaclarStringCollection Phones { get; set; } = new ScalarStringCollection();
}

コード:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;

namespace System.Collections.Specialized
{
#if NET462
  [ComplexType]
#endif
  public abstract class ScalarCollectionBase<T> :
#if NET462
    Collection<T>,
#else
    ObservableCollection<T>
#endif
  {
    public virtual string Separator { get; } = "\n";
    public virtual string ReplacementChar { get; } = " ";
    public ScalarCollectionBase(params T[] values)
    {
      if (values != null)
        foreach (var item in Items)
          Items.Add(item);
    }

#if NET462
    [Browsable(false)]
#endif
    [EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("Not to be used directly by user, use Items property instead.")]
    public string Data
    {
      get
      {
        var data = Items.Select(item => Serialize(item)
          .Replace(Separator, ReplacementChar.ToString()));
        return string.Join(Separator, data.Where(s => s?.Length > 0));
      }
      set
      {
        Items.Clear();
        if (string.IsNullOrWhiteSpace(value))
          return;

        foreach (var item in value
            .Split(new[] { Separator }, 
              StringSplitOptions.RemoveEmptyEntries).Select(item => Deserialize(item)))
          Items.Add(item);
      }
    }

    public void AddRange(params T[] items)
    {
      if (items != null)
        foreach (var item in items)
          Add(item);
    }

    protected abstract string Serialize(T item);
    protected abstract T Deserialize(string item);
  }

  public class ScalarStringCollection : ScalarCollectionBase<string>
  {
    protected override string Deserialize(string item) => item;
    protected override string Serialize(string item) => item;
  }

  public class ScalarCollection<T> : ScalarCollectionBase<T>
    where T : IConvertible
  {
    protected override T Deserialize(string item) =>
      (T)Convert.ChangeType(item, typeof(T));
    protected override string Serialize(T item) => Convert.ToString(item);
  }
}

8
少しやりすぎに見えますか?!
Falco Alexander

1
@FalcoAlexander私は自分の投稿を更新しました...多分少し冗長ですが、仕事をします。NET462適切な環境に置き換えるか、環境に追加してください。
Shimmy Weitzhandler 2017年

1
これをまとめる努力の+1。解決策は、文字列の配列を格納するための少しやり過ぎです:)
GETah
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.