使用Entity Framework将集合保存为JSON

11

我正在寻找一种方法,使一个对象成为一个集合,但是当它保存到数据库时变为一个JSON字符串。 我该如何设置Entity Framework 6.1来实现这个目标? 例如:

 public class Company{

    public Company(){
       this.Times = new HashSet<DateTime>();
    }

    public int Id {get;set;}

    public string Name {get;set;}

    public List<DateTime> Times {get;set;}

 }

公司是一个实体对象。我希望将时间以JSON字符串的形式存储在数据库中。读取时,希望将其序列化为日期时间列表。保存时,希望将列表转换回JSON字符串并保存。

2个回答

16

被接受的答案存在问题,即列表内容的任何更改(添加、修改或删除条目)都不会被EF跟踪

这是我的解决方案,受到这篇优秀博客文章的启发。

这个类负责序列化和反序列化,以便可以将集合存储在父模型的单个列中:

[ComplexType]
public class DateTimeCollection : Collection<DateTime>
{
    public void AddRange(IEnumerable<DateTime> collection)
    {
        foreach (var item in collection)
        {
            Add(item);
        }
    }

    [Column("Times")]
    public string Serialized
    {
        get { return JsonConvert.SerializeObject(this); }
        private set
        {
            if (string.IsNullOrEmpty(value))
            {
                Clear();
                return;
            }

            var items = JsonConvert.DeserializeObject<DateTime[]>(value);
            Clear();
            AddRange(items);
        }
    }
}

就这样!现在你可以像预期的那样在父类中使用这个新集合。集合内容的更改将被跟踪。

public class Company{
    // ...
    public DateTimeCollection Times { get; set; }
}

6
很棒的答案。值得注意的是,你可以在DBContext中定义复杂类型的方法如下:modelBuilder.ComplexType() .Property(p => p.Serialized) .HasColumnName("Times"); - Sudarshan_SMD
你有没有想过如何通过EF使这个Json列可查询?你能否覆盖这个属性的“Filter/Where”实现? - Terence
1
EF6 中,我们仍然缺乏 EF CoreValueConvertersProperty().HasConversion() 功能。 接受的答案建议为每个 Collection 使用单独的“扁平化”值,这会污染 Model。 这是绝对不可取的! 这个解决方案是迄今为止最干净的解决方案。 虽然在这个自定义的 Collection 实现上使用 Data Annotation Column 会污染 Collection 本身,但它不应与 EF 相关联。 要解决此问题,只需使用 Fluent API 并在 DbContext.OnModelCreating(ModelBuilder) 中添加 Property(i => i.Times.Serialized).HasColumnName("Times"); 即可。 - Efthymios

6
以下代码应该可以工作(我使用的是Json.Net,但你可以更换为任何其他序列化器):
public class Company
{

    public int Id {get;set;}

    public string Name {get;set;}

    [NotMapped]
    public List<DateTime> Times {get;set;}

    [Column("Times")]
    public string TimesSerialized
    {
        get
        {
            return JsonConvert.SerializeObject(Times);
        }
        set
        {
            Times = string.IsNullOrEmpty(value)
                    ? new List<DateTime>()
                    : JsonConvert.DeserializeObject<List<DateTime>>(value);
        }
    }
}

如果您手动映射,也可以将TimesSerialized设置为私有。

1
@rleffler 请查看关于私有属性的答案:https://dev59.com/JmYr5IYBdhLWcg3wRoSW#13810766 - DixonD
@DixonD 我有点困惑。set 应该序列化列表(因为它将作为字符串保存在数据库中),而 get 则反序列化它,是吗? - kanpeki
@kanpeki 当EF从数据库读取实体时,它会调用该集合。因此,它调用该集合并传递字符串DB值,因此此setter对其进行反序列化并更新Times属性。而对于get,当EF将实体保存到数据库时,它也会被调用。 - DixonD

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接