PostgreSQL具有Jsonb数据类型,它允许您向标准关系模型中添加附加属性,并能够搜索它们。
具有Npgsql扩展名的EntityFramework Core可以将字段数据拉到System.String
类型
但是,要在查询级别通过EF过滤Json属性,您必须使用纯SQL,这不是很方便,因为您需要进入映射(如果不是自动的),请查找与模型属性相对应的字段名称,以支持此命名。 ORM给我们带来的灵活性已丧失。
如果它使您和我都感到沮丧,欢迎猫来。
在文章的末尾有一个到源的链接!
表示任务
作为一名开发人员,我想要一个工具来访问Jsonb字段,目的是按它们进行过滤和排序,它是:
- 它将与EntityFramework Core 2兼容(我们使用它)
- 使用它不需要您自己编写SQL
- 将与平面Json结构一起使用(在json内只有json属性)
我将添加Npgsql.Json.NET ,它可以将Json和Jsonb值投影到CLR类型中。 老实说,我不明白这可能是什么意思,因为由于我们在关系数据库中需要一个Json字段,因此很可能我们拥有具有动态字段集的实体。
解决问题的算法
- 定义可以满足我们需求的一种或多种方法。
- 创建一个将参与SQL代码生成的翻译器。
- 全部拧到Npgsql。
解决方案
首先,我们定义一个方法。 我希望它被这样使用:
context.Entity.Where(x => JsonbMethods.Value<string>(x.JsonbField, "jsonPropertyName") == "value")
因此,这是我们的方法:
public static TSource Value<TSource>(object jsonbProperty, string jsonbPropertyName) { throw new NotSupportedException(); }
几个小时以来,我一直在选择EF Core,Npgsql的资源,而不仅仅是在寻找扩展SQL生成的基本功能的方法。 我看了这篇文章 ,但是我不喜欢作者使用的连接翻译器方法的方法,因为它重新定义了标准工具,这意味着它可能与另一个类似的工具发生冲突。
结果,我找到了Net Topology Suite的源代码。 我所需要的只是一种方法连接方法转换器。
但是大多数时间我都花在生成所需的sql片段上。
这是必需的语法
tableAlias."JsonField"->>"insideProperty"
最初,我尝试在翻译器中返回ColumnExpression。 创建它时,第一个参数是列名(字符串)。 我只是根据方法中提供的参数来烹饪它。 启动,检查,错误。 事实证明,我通过的名字用引号引起来。 结果,SQL原来是tableAlias.""JsonField"->>"insideProperty""
。
在生成器的源代码中,我发现了VisitColumn
方法,该行为是硬编码的,并且不依赖于任何参数。 也就是说,我不能影响它。 有必要寻找另一种解决方案。
然后,我创建了自己的Expression
- JsonbPropertyAccessorExpression: Expression
仍然需要重写其ISqlExpressionVisitor
Accept
方法。
但麻烦的是,在此接口中没有自定义运算符可以分段的方法。 然后,我想到访问的不是一种方法,而是几种。 首先访问VisitColumn
,它创建对tableAlias。“ JsonField”列的访问,然后VisitSqlFragment
,在其中添加了"->>'insideFieldName'"
。
我没有希望,但是成功了。 差不多了
当我出于某种原因而尝试按文本进行过滤时,由于某种原因, tableAlias."JsonField"->>"insideProperty" = JSONB "value"
这样一个tableAlias."JsonField"->>"insideProperty" = JSONB "value"
过滤器: tableAlias."JsonField"->>"insideProperty" = JSONB "value"
,这导致了错误,因为如果文本不包含有效的Json,则无法将其转换为JSONB类型。 为何在我需要文字时需要引导某事?
我什至决定从映射模型的Jsonb列中删除标记,即Jsonb,仅将此标记添加到MigrationContext
以便生成正确的迁移。 它甚至起飞了,但是在我看来,这种方法似乎很困难。 不过,我停在那里。
之后,我将设置为CAST,因为Value
方法是通用的,并且Json属性中可以有不同类型的数据,这些数据也需要排序和过滤。
结果,我开始从我的翻译器返回ExplicitCastExpression
,在其中传递了我自定义的Expression
和Value
方法的通用参数中包含的类型。
当我按日期搜索结果SQL时,我发现比较的值被强制转换为时间戳类型。 timestamp 'some date value'
。 然后它突然降临在我身上。 我用拐杖解决了以前的问题,它自己消失了。 当将访问器转换为Json字段的文本时,生成器不再向JSONB添加显式转换,因为比较操作已经在左侧包含文本,并且默认情况下,Jsonb字段的访问器返回Jsonb类型。
最后
最后,我想补充一点,我没有找到有关如何添加属性和方法的自定义转换器的文档。 可能看起来很糟糕。 如果有人对方法,代码等有评论,请在评论中写下。
如果有人想分叉扩展库,请写一封私人信件,我将尽力提供帮助。 好吧,还是抛出pullrequests。
这是到源的链接