实体框架 - 代码优先 - 无法存储列表<String>

Pau*_*aul 82 .net c# entity-framework

我写了这样的课:

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"
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

internal class DataContext : DbContext
{
    public DbSet<Test> Tests { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

运行代码后:

var db = new DataContext();
db.Tests.Add(new Test());
db.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

我的数据正在保存但只是保存Id.我没有任何表格,也没有适用于字符串列表的关系.

我究竟做错了什么?我也尝试制作字符串, virtual但它没有改变任何东西.

谢谢您的帮助.

Paw*_*wel 149

实体框架不支持基本类型的集合.您可以创建实体(将保存到不同的表)或执行一些字符串处理以将列表保存为字符串,并在实体实现后填充列表.


Sas*_*san 49

EF Core 2.1+:

属性:

public string[] Strings { get; set; }
Run Code Online (Sandbox Code Playgroud)

OnModelCreating:

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

  • EF Core的绝佳解决方案。虽然似乎有一个问题,将char转换为字符串。我必须像这样实现它:.HasConversion(v =&gt; string.Join(“;”,v),v =&gt; v.Split(new char [] {';'},StringSplitOptions.RemoveEmptyEntries)); (5认同)
  • 这是唯一真正正确的答案恕我直言.所有其他人都要求您更改模型,这违反了域模型应该是持久性无知的原则.(如果您使用单独的持久性和域模型,那很好,但实际上很少有人这样做.) (5认同)
  • 如果我的任何字符串包含“,”,则会中断 (3认同)
  • 你应该接受我的编辑请求,因为你不能使用 char 作为 string.Join 的第一个参数,如果你还想提供 StringSplitOptions,你必须提供一个 char[] 作为 string.Split 的第一个参数。 (2认同)
  • 在.NET Core中可以。我在一个项目中使用了这段确切的代码。 (2认同)
  • 在.NET Standard中不可用 (2认同)
  • 这个方法确实很hacky。无论您选择加入哪个字符,都存在包含该字符的字符串在某个时刻被添加到数组中的风险,这将导致数据损坏。如果没有充分的理由,我建议不要使用此方法。 (2认同)

ran*_*oms 41

我知道这是一个老问题,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(); }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 有一个私有列表,其中有两个关联的公共属性:字符串(将在应用程序中用于添加和删除字符串),以及StringsAsString(将作为逗号分隔列表保存到数据库的值)。我不确定我要问的是什么,绑定是私有列表_strings,它将两个公共属性连接在一起。 (2认同)
  • 请记住,这个答案不会在字符串中转义“,”(逗号)。如果列表中的字符串包含一个或多个“,”(逗号),则该字符串将拆分为多个字符串。 (2认同)
  • 在`string.Join`中,逗号应该用双引号(对于字符串)而不是单引号(对于char)包围。参见https://msdn.microsoft.com/zh-cn/library/57a79xd0(v=vs.110).aspx (2认同)

CAD*_*oke 26

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);
    }
}
Run Code Online (Sandbox Code Playgroud)


Xan*_*iff 21

稍微调整@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));
Run Code Online (Sandbox Code Playgroud)

请注意,虽然Serialize()and 中的第二个参数Deserialize()通常是可选的,但您会收到错误消息:

表达式树不能包含使用可选参数的调用或调用

将其显式设置为默认值(空)会清除它。

  • 在 .NET 6 中,您将收到此错误:**以下方法或属性之间的调用不明确:'JsonSerializer.Serialize(TValue, JsonSerializerOptions?)' 和 'JsonSerializer.Serialize(TValue, JsonTypeInfo)** 所以您'需要将默认值转换为“JsonSerializerOptions”,如下所示:“(JsonSerializerOptions)default” (4认同)
  • 我正在考虑更新我的答案以添加新的序列化替代方案。我真的很喜欢使用“default”作为可选参数技巧。使得代码非常干净。 (2认同)

Mat*_*LES 16

该答案基于@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));
Run Code Online (Sandbox Code Playgroud)

使用EF Core流畅配置,我们List可以对JSON进行序列化/反序列化。

为什么此代码是您可以争取的一切的完美结合:

  • Sasn最初的答案的问题是,如果列表中的字符串包含逗号(或选择作为分隔符的任何字符),它将变成一个大混乱,因为它将单个条目转换为多个条目,但这是最容易阅读和阅读的。最简洁。
  • CAD bloke答案的问题在于它丑陋且需要更改模型,这是一种不良的设计实践(请参阅Marcell Toth对Sasan答案的评论)。但这是数据安全的唯一答案。

  • 太棒了,这应该*可能*是被接受的答案 (3认同)
  • 您能够查询该字段吗?我的尝试惨遭失败: `var result = wait context.MyTable.Where(x =&gt; x.Strings.Contains("findme")).ToListAsync();` 没有找到任何东西。 (3认同)
  • 为了回答我自己的问题,引用[文档](https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions#limitations):“使用值转换可能会影响 EF 的能力将表达式转换为 SQL 的核心。对于这种情况,将记录一条警告。正在考虑在未来版本中删除这些限制。” - 不过还是会很好。 (3认同)

Ada*_*Tal 11

只是为了简化 -

实体框架不支持原语.您可以创建一个类来包装它,也可以添加另一个属性来将列表格式化为字符串:

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

  • 另外,不要忘记在ICollection属性上使用[NotMapped] (3认同)

小智 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"
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 如果字符串包含逗号怎么办? (4认同)
  • 我不建议这样做。仅当更改Strings _reference_时,才会更新StringsAsStrings,并且在您的示例中唯一发生的是分配时。在分配后在“字符串”列表中添加或删除项目将_not_更新`StringsAsStrings`支持变量。实现此目的的正确方法是将StringsAsStrings作为Strings列表的视图公开,而不是相反。在StringsAsStrings属性的get访问器中将这些值连接在一起,并在set访问器中将它们拆分。 (4认同)

小智 7

文档链接

文档中的示例:

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Contents { get; set; }

    public ICollection<string> Tags { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

使用 System.Text.Json:

modelBuilder.Entity<Post>()
    .Property(e => e.Tags)
    .HasConversion(
        v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
        v => JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions)null),
        new ValueComparer<ICollection<string>>(
            (c1, c2) => c1.SequenceEqual(c2),
            c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
            c => (ICollection<string>)c.ToList()));
Run Code Online (Sandbox Code Playgroud)