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
, но ничего не изменил.
Благодарим вас за помощь.
Ответы
Ответ 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 для сохранения в базе данных и 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
другое решение, которое определяет имя столбца, было объяснено здесь