Подтвердить что ты не робот

Entity Framework - первый код - не удается сохранить список <String>

Я написал такой класс:

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, но ничего не изменил.

Благодарим вас за помощь.

4b9b3361

Ответ 1

Entity Framework не поддерживает коллекции примитивных типов. Вы можете создать объект (который будет сохранен в другой таблице) или выполнить некоторую строковую обработку, чтобы сохранить ваш список в виде строки и заполнить список после того, как сущность материализовалась.

Ответ 2

Я знаю, что это старый вопрос, и 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(); }
    }
}

Ответ 3

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));

Ответ 4

JSON.NET для спасения.

Сериализуйте его в JSON для сохранения в базе данных и Deserialize для восстановления коллекции .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);
    }
}

Ответ 5

Просто для упрощения -

Структура сущности не поддерживает примитивы. Вы либо создаете класс для его переноса, либо добавляете другое свойство для форматирования списка в виде строки:

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

Ответ 6

Конечно Правильный ответ дал Павел. Но я нашел в этом сообщении, что с 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"
        };
    }
}

Ответ 7

Вы можете использовать этот контейнер 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

Этот ответ основан на ответах @Sasan и @CAD bloke.

Работает только с EF Core 2. 1+ (не совместим со стандартом .NET) (Newtonsoft JsonConvert)

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

Используя свободную конфигурацию EF Core, мы сериализуем/десериализуем List в/из JSON.

Почему этот код является идеальным сочетанием всего, к чему вы могли стремиться:

  • Проблема с оригинальным ответом Sasn состоит в том, что он превратится в большой беспорядок, если строки в списке содержат запятые (или любой символ, выбранный в качестве разделителя), потому что он превратит одну запись в несколько записей, но ее легче всего читать и самый лаконичный
  • Проблема с ответом типа "CAD CAD" состоит в том, что он уродлив и требует изменения модели, что является плохой практикой проектирования (см. Комментарий Marcell Toth к ответу Sasan). Но это единственный ответ, который безопасен для данных.

Ответ 9

другое решение, которое определяет имя столбца, было объяснено здесь