EF Core 3.1 无法查询 Json 序列化对象

Posted

技术标签:

【中文标题】EF Core 3.1 无法查询 Json 序列化对象【英文标题】:EF Core 3.1 Fail to query on Json Serialized Object 【发布时间】:2021-01-20 01:07:39 【问题描述】:

我使用 json 序列化将列表存储在字段中的 id 上

型号:

public class Video

    public int Id  get; set; 
    public string Name  get; set; 
    public virtual IList<int> AllRelatedIds  get; set; 


上下文:

modelBuilder.Entity<Video>(entity =>

    entity.Property(p => p.AllRelatedIds).HasConversion(
    v => JsonConvert.SerializeObject(v, new JsonSerializerSettings  NullValueHandling = NullValueHandling.Ignore ),
    v => JsonConvert.DeserializeObject<IList<int>>(v, new JsonSerializerSettings  NullValueHandling = NullValueHandling.Ignore )
    );
);

它工作正常,添加、编辑、删除项目很容易,并且在 SQL 数据库中它以 json 格式存储,如 [11000,12000,13000]

一切都很好但是!只要想在这个列表上查询,我就会得到奇怪的响应。

地点:

_context.Set&lt;Video&gt;().Where(t=&gt;t.AllRelatedIds.contains(11000)) 返回 null 但是,如果我要求返回所有 AllRelatedIds 项,则某些记录的值 exp 为 11000。

计数:

_context.Set&lt;Video&gt;().Count(t=&gt;t.AllRelatedIds.contains(11000)) 返回无法翻译。以可翻译的形式重写查询,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用显式切换到客户端评估。

EF Core 有什么问题?我什至测试了t=&gt;t.AllRelatedIds.ToList().contains(11000),但没有任何区别

我该怎么办?我不想有更多的表,我使用了数百次这种方法,但似乎从未对它们进行查询。

【问题讨论】:

我想问问这个数据库是怎么回事?您是否尝试将 ID 存储在 JSON 字段而不是多对多表中?这是一个明显的数据库设计错误。 EF是一个ORM,它将对象映射到关系结构,它不能做数据库中不可能的事情。使用多对多表很容易、干净且易于管理。将所有内容都填充到一个字段中既不是,因为它违反了最基本的设计规则——一个字段,一个值。现在不可能使用 SQL 来连接、过滤或查询该字段 如果您使用 SQL Server 2016 及更高版本,您可以使用 JSON 函数(JSON_QUERY、JSON_VALUE)来使用该字段,但您仍然会得到非常糟糕的性能,因为这些值可以'不被索引。如果您有像 Oracle 这样支持阵列的数据库,您可以使用 VARRAY,但性能仍然会受到影响 I don't want to have more tables, 为什么? as soon as want to query on this list 你没有列表,你有一个很长的不透明字符串,一些函数可以解析。如果数据库设计得当,您甚至可以使用SQL Server's graph features 跨多个级别查找相关视频。通过使用那个长字符串,您甚至无法在 SQL 中查询单个值 @PanagiotisKanavos 我不想在不重要的项目上使用太多的关系并进行 sql 查询,只是让它们像逗号分隔似乎就足够了 @PanagiotisKanavos 你是对的,我必须将它们存储在数据库中 【参考方案1】:

Json 序列化/反序列化发生在应用程序级别。 EF Core 将IList&lt;int&gt; 对象序列化为值[11000,12000,13000] 之前 将其发送到数据库进行存储,并将值[11000,12000,13000] 反序列化为IList&lt;int&gt; 对象之后 检索它从数据库。数据库内部什么也没有发生。您的数据库无法将 [11000,12000,13000] 作为数字集合进行操作。对数据库来说,它是一条数据。

如果您尝试以下查询 -

var videos = _context.Set<Video>().ToList();
var video = _context.Set<Video>().FirstOrDefault(p=> p.Id == 2);

你会得到预期的结果,EF Core 完美地完成了它的工作。

问题是,当你查询类似 -

_context.Set<Video>().Where(t=> t.AllRelatedIds.Contains(11000))

EF Core 将无法将 t.AllRelatedIds.Contains(11000) 部分转换为 SQL。 EF Core 只能序列化/反序列化它,因为您告诉它(以及如何)。但正如我上面所说,您的数据库不能将[11000,12000,13000] 作为整数集合进行操作。因此 EF Core 无法将 t.AllRelatedIds.Contains(11000) 转换为对数据库有意义的任何内容。

解决方案是获取所有视频的列表,以便 EF Core 可以将 AllRelatedIds 反序列化为 IList&lt;int&gt;,然后您可以对其应用 LINQ -

var allVideos = _context.Set<Video>().ToList();
var selectedVideos = allVideos.Where(t=> t.AllRelatedIds.Contains(11000)).ToList();

但是从性能的角度来看,不是每次都获取所有视频是不必要的/矫枉过正或效率低下吗?是的当然。但正如 cmets 所暗示的,您的数据库设计/使用方法存在一些缺陷。

【讨论】:

以上是关于EF Core 3.1 无法查询 Json 序列化对象的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework Core 数据查询原理详解

3.1 ef core

EF Core查询jsonb

EF Core 3.1 在特定数据库上下文上添加迁移

EF Core 7.0 – JSON Column

EF CORE中复杂类型的映射