You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过EF自定义函数映射在单元测试中模拟EF.Functions.ILike

解决EF.Functions.ILike单元测试无法翻译的问题

方案1:自定义DbFunction映射(推荐)

针对单元测试环境,通过注册自定义DbFunction,让EF能将ILike调用转换为客户端可执行的大小写不敏感匹配逻辑:

1. 定义测试用ILike实现

在测试项目中创建扩展方法,模拟PostgreSQL ILIKE的核心行为:

public static class TestDbFunctions
{
    public static bool ILike(this DbFunctions _, string input, string pattern)
    {
        if (string.IsNullOrEmpty(input)) return false;
        // 处理%通配符,转换为大小写不敏感的包含匹配
        var cleanPattern = pattern.Replace("%", "");
        return input.IndexOf(cleanPattern, StringComparison.OrdinalIgnoreCase) >= 0;
    }
}

2. 在测试DbContext中注册函数映射

在测试用的DbContext(或测试时的上下文配置)的OnModelCreating中,注册该方法并指定翻译逻辑:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    var ilikeMethod = typeof(TestDbFunctions)
        .GetMethod(nameof(TestDbFunctions.ILike), 
            new[] { typeof(DbFunctions), typeof(string), typeof(string) });

    modelBuilder.HasDbFunction(ilikeMethod)
        .HasTranslation(args =>
        {
            // args[0] = DbFunctions实例(无实际作用),args[1] = 字段值,args[2] = 匹配模式
            var input = args[1];
            var pattern = args[2];

            // 移除模式中的%通配符,转换为大小写不敏感的Contains调用
            var cleanPattern = Expression.Call(
                pattern,
                typeof(string).GetMethod(nameof(string.Replace), new[] { typeof(string), typeof(string) }),
                Expression.Constant("%"),
                Expression.Constant("")
            );

            return Expression.Call(
                typeof(string),
                nameof(string.Contains),
                Type.EmptyTypes,
                input,
                cleanPattern,
                Expression.Constant(StringComparison.OrdinalIgnoreCase)
            );
        });
}

3. 单元测试中使用该上下文

在测试时,使用配置了上述映射的DbContext(比如InMemory数据库),即可正常执行包含EF.Functions.ILike的查询。

方案2:使用查询拦截器替换表达式

如果不想修改DbContext配置,可以通过EF查询拦截器,将查询中的ILike调用替换为客户端可处理的逻辑:

public class ILikeInterceptor : ExpressionVisitor, IQueryExpressionInterceptor
{
    public Expression QueryCompilationStarting(Expression queryExpr, QueryExpressionEventData eventData)
        => Visit(queryExpr);

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        // 识别Npgsql的ILike扩展方法调用
        if (node.Method.DeclaringType == typeof(NpgsqlDbFunctionsExtensions) &&
            node.Method.Name == nameof(NpgsqlDbFunctionsExtensions.ILike))
        {
            var input = node.Arguments[1];
            var pattern = node.Arguments[2];

            // 转换为大小写不敏感的Contains逻辑
            var cleanPattern = Expression.Call(
                pattern,
                typeof(string).GetMethod(nameof(string.Replace), new[] { typeof(string), typeof(string) }),
                Expression.Constant("%"),
                Expression.Constant("")
            );

            return Expression.Call(
                typeof(string),
                nameof(string.Contains),
                Type.EmptyTypes,
                input,
                cleanPattern,
                Expression.Constant(StringComparison.OrdinalIgnoreCase)
            );
        }

        return base.VisitMethodCall(node);
    }
}

测试时注册拦截器:

var options = new DbContextOptionsBuilder<MyDbContext>()
    .UseInMemoryDatabase("TestDB")
    .AddInterceptors(new ILikeInterceptor())
    .Options;

using var context = new MyDbContext(options);

注意事项

  • 上述代码针对%xxx%格式的通配符做了简化,若需支持_单字符匹配等复杂通配符,可在逻辑中添加正则转换(比如将_替换为.,%替换为.*后用正则匹配)。
  • 两种方案均无需修改生产代码,仅在单元测试环境中配置即可。

内容的提问来源于stack exchange,提问作者dgad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 01:05:02