Entity Framework-코드 우선-목록을 저장할 수 없음 <문자열>


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. 문자열 목록에 적용되는 테이블이나 관계가 없습니다 .

내가 뭘 잘못하고 있죠? 나는 또한 Strings 를 만들려고 시도했지만 virtual아무것도 변경하지 않았습니다.

도와 주셔서 감사합니다.


3
List <sting>이 ​​db에 어떻게 저장 될 것으로 예상합니까? 작동하지 않습니다. 문자열로 변경하십시오.
Wiktor Zychla

4
목록이있는 경우 일부 항목을 가리켜 야합니다. EF가 목록을 저장하려면 두 번째 테이블이 필요합니다. 두 번째 테이블에서는 목록의 모든 항목을 넣고 외래 키를 사용하여 Test엔터티를 다시 가리 킵니다 . 그러니 Id속성과 MyString속성 으로 새 엔티티 를 만들고 그 목록을 만드세요.
Daniel Gabriel

1
맞아 ... DB에 직접 저장할 수는 없지만 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
이것은 유일한 정답 IMHO입니다. 다른 모든 것은 모델 변경을 요구하며 도메인 모델은 영속성을 무시해야한다는 원칙을 위반합니다. (별도의 지속성 및 도메인 모델을 사용하고 있지만, 몇 사람들이 실제로 그렇게 할 경우는 괜찮습니다.)
마르셀 토스

2
char를 string.Join의 첫 번째 인수로 사용할 수없고 StringSplitOptions도 제공하려면 char []를 string.Split의 첫 번째 인수로 제공해야하므로 내 편집 요청을 수락해야합니다.
Dominik

2
.NET Core에서 할 수 있습니다. 내 프로젝트 중 하나에서이 정확한 코드를 사용하고 있습니다.
Sasan 2019

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 유창한 구성을 사용하여 ListJSON으로 /에서 직렬화 / 역 직렬화합니다 .

이 코드가 노력할 수있는 모든 것의 완벽한 조합 인 이유 :

  • Sasn의 원래 답변의 문제점은 목록의 문자열에 쉼표 (또는 구분 기호로 선택한 문자)가 포함되어 있으면 단일 항목을 여러 항목으로 바꾸지 만 읽기가 가장 쉽고 읽기 쉽고 가장 간결합니다.
  • CAD 블로크의 답변의 문제점은보기 흉하고 모델을 변경해야한다는 것입니다. 이는 잘못된 설계 관행입니다 ( Sasan의 답변 에 대한 Marcell Toth의 의견 참조 ). 그러나 데이터에 안전한 유일한 대답입니다.

7
브라보,이해야 아마 있을 허용 대답
Shirkan

1
.NET Framework 및 EF 6에서 작동했으면 좋겠습니다. 정말 우아한 솔루션입니다.
CAD

이것은 놀라운 해결책입니다. 감사합니다
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 개의 목록을 정의해야합니까? 하나는 속성으로 하나는 실제 목록으로? 이 솔루션이 저에게 잘 작동하지 않고 여기서 바인딩을 파악할 수 없기 때문에 여기서 바인딩이 어떻게 작동하는지 설명해 주시면 감사하겠습니다. 감사합니다
LiranBo

2
두 개의 공용 속성이 연결되어있는 하나의 개인 목록, 즉 응용 프로그램에서 문자열을 추가 및 제거하는 데 사용할 Strings와 쉼표로 구분 된 목록으로 db에 저장 될 값인 StringsAsString이 있습니다. 나는 당신이 무엇을 요구하고 있는지 잘 모르겠습니다. 바인딩은 두 개의 공용 속성을 함께 연결하는 개인 목록 _strings입니다.
불규칙적

1
이 답변은 ,문자열에서 이스케이프 (쉼표) 가 아닙니다 . 목록의 문자열에 하나 이상의 ,(쉼표)가 포함되어 있으면 문자열이 여러 문자열로 분할됩니다.
Jogge 2017-04-05

2
에서 string.Join쉼표 (문자열에 대한) 큰 따옴표,하지 (숯불에 대한) 작은 따옴표로 묶어야합니다. 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]했으므로 정상적인 코딩 과정에서 "이것은 당신이 찾고있는 속성이 아닙니다"라는 것이 매우 분명 할 것입니다. "real"속성에는 태그가 지정 [NotMapped]되므로 Entity 프레임 워크는이를 무시합니다.

(관련되지 않은 탄젠트) : 더 복잡한 유형으로 동일한 작업을 수행 할 수 있지만 해당 객체의 속성을 쿼리하기가 너무 어렵게 만들었나요? (예, 제 경우에는).

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
나는 다른 하나의 강점을 사용하여 각 대답 문제 (추악함 / 데이터 안전)를 수정하기 위해이 문제와 다른 하나를 "병합" 하는 답 을 만들었습니다 .
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 2015

3
또한, ICollection이 속성에 [NotMapped]를 사용하는 것을 잊지 마세요
벤 피터슨

7

물론 Pawel은 정답을 제시했습니다 . 그러나이 게시물 에서 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
문자열에 쉼표가 있으면 어떻게됩니까?
Chalky

4
이런 식으로하는 것은 권장하지 않습니다. 참조 가 변경 StringsAsStrings될 때만 업데이트 되며 예제에서 발생하는 유일한 시간은 할당입니다. 할당 후 목록 에서 항목을 추가하거나 제거해도 백업 변수가 업데이트 되지 않습니다 . 이를 구현하는 적절한 방법 은 다른 방법 대신 목록 의보기 로 노출 하는 것입니다. 속성 의 접근 자 에서 값을 함께 결합하고 접근 자에서 분할합니다 . Strings StringsStringsAsStringsStringsAsStringsStringsgetStringsAsStringsset
jduncanator 2017 년

(부작용이없는) private 속성 추가를 방지하려면 serialize 된 속성의 setter를 private으로 만듭니다. jduncanator는 물론 맞습니다. 목록 조작 (ObservableCollection 사용?)을 포착하지 못하면 EF가 변경 사항을 알아 차리지 못합니다.
Leonidas

@jduncanator가 언급했듯이이 솔루션은 목록을 수정하면 작동하지 않습니다 (예 : MVVM에서 바인딩)
Ihab Hajj

7

@Mathieu Viales대답을 약간 조정 하면 새로운 System.Text.Json 직렬 변환기를 사용하여 .NET Standard 호환 코드 조각이 있으므로 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.