如何通过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
相关产品推荐
相关产品推荐

