EF Core 5 - 如何将 EF.Functions.Like 与映射到 JSON 字符串的自定义属性一起使用?

Posted

技术标签:

【中文标题】EF Core 5 - 如何将 EF.Functions.Like 与映射到 JSON 字符串的自定义属性一起使用?【英文标题】:EF Core 5 - How can I use EF.Functions.Like with a custom property that maps to a JSON string? 【发布时间】:2021-09-08 12:01:39 【问题描述】:

在我的一个数据库模型中,我有一个自定义类型 (Dictionary<string, string>) 的属性,该属性使用自定义转换器自动转换为 JSON 或从 JSON 转换,并存储为数据库中的 text 字段。我希望能够使用 mysqlLIKE 比较器在这个 JSON 字段中搜索一个字符串,但是我遇到了一个异常。我不关心 JSON 的结构,我可以将其视为一个简单的文本字段并以这种方式进行搜索。

这是我尝试的方法(Sku 是复杂类型):

var responseSet = database.Products.Where(p => EF.Functions.Like(p.Sku, "%query%"));

这是我得到的例外:

An unhandled exception has occurred while executing the request.
System.InvalidOperationException: The LINQ expression 'DbSet<ProductObject>()
    .Where(p => __Functions_0
        .Like(
            matchExpression: p.Sku, pattern: __Format_1))' could not be translated. 
            Additional information: 
              Translation of method 'Microsoft.EntityFrameworkCore.MySqlDbFunctionsExtensions.Like' 
              failed. If this method can be mapped to your custom function, 
              see https://go.microsoft.com/fwlink/?linkid=2132413 for more information. 

异常中的链接指向一个带有大量交叉引用的长 git 问题,但我无法在其中找到任何有用的东西。

有什么方法可以防止此错误发生并在复杂字段中进行搜索?

【问题讨论】:

【参考方案1】:

EF Core value converters 的基本问题是 LINQ 查询是针对客户端类型构建的,然后在后台将其转换为提供程序类型,并且没有标准方法来指定查询内的提供程序类型转换。

但是,转换有一个简单的技巧(在我对其他转换相关问题的一些回答中,例如 How can a JSON_VALUE be converted to a DateTime with EF Core 2.2?、Expression tree to SQL with EF Core 或 Comparing strings as dates using EF core 3),它适用于大多数 EF Core 关系数据库提供程序。

在这种情况下,您知道提供程序类型的 CLR 等效项是 string,因此您可以使用以下强制转换

p => EF.Functions.Like((string)(object)p.Sku, "%query%")

需要到object 的中间转换来欺骗C# 编译器以接受实际的转换。 EF Core 翻译器足够聪明,可以将其删除(以及此处不需要的其他翻译器)。

【讨论】:

【参考方案2】:

这不是一个受支持的功能,但有一个可能对您有用的解决方法。定义自定义数据库函数以将表达式显式转换为底层 db 类型。

public static string Cast(YourComplexType t) => throw new NotSupportedException();

modelBuilder.HasDbFunction(() => Cast(default))
    .HasTranslation(args =>
    
        var a = args.First();
        return new SqlUnaryExpression(
            ExpressionType.Convert, 
            a, 
            typeof(string), 
            new StringTypeMapping(a.TypeMapping.StoreType, DbType.String));
    );

EF.Functions.Like(Cast(p.Sku), "%query%")

很遗憾,您无法定义通用数据库函数。参数都必须是提供者可以存储的类型,这排除了object 或接口。因此,您可能必须为每种可能的类型定义重载方法。

【讨论】:

谈到骇人听闻的事情,欺骗 C# 编译器的“双重转换”技术通常对大多数 EFC 关系数据库提供程序都有效。例如EF.Functions.Like((string)(object)p.Sku, "%query%") 两个答案似乎都是正确的,但@IvanStoev 似乎更简单,而且效果很好。如果您将其移至单独的答案,我会投票赞成。

以上是关于EF Core 5 - 如何将 EF.Functions.Like 与映射到 JSON 字符串的自定义属性一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

EF Core 5,删除多对多关系

如何使用 EF Core 5 执行存储过程 ORACLE?

从 EF Core 5 迁移到 EF Core 6 时出错

如何在 EF Core 5 中为自定义 SQL 配置导航属性

C# 数据操作系列 - 5. EF Core 入门

如何配置 EF Core 5 以使 MS SQL Server 数据库生成 Guid 密钥