如何在单元测试期间通过 EF 用户定义的函数映射来模拟 EF.Functions.ILike

问题描述 投票:0回答:1

我更改了当前代码

_context.TABLE.Where(x => x.COLUMN1.Contains("xxx") || x.COLUMN2.Contains("xxx"))

进入

_context.TABLE.Where(x => EF.Functions.ILike(x.COLUMN1, "%xxx%") || EF.Functions.ILike(x.COLUMN2, "%xxx%")))

这满足了我的需要——不区分大小写的搜索。但它破坏了许多已经完成的单元测试。

异常信息: 

System.InvalidOperationException : The LINQ expression '...' could not be translated. Additional information: Translation of method 'Microsoft.EntityFrameworkCore.NpgsqlDbFunctionsExtensions.ILike' failed. If this method can be mapped to your custom function, see https://go.microsoft.com/fwlink/?linkid=2132413 for more information. ... Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

我想将 ILike 函数的原子逻辑注入到提供程序中,以便它可以使用它 - 只是在单元测试的情况下。我读了很多书,似乎它不能像我想象的那样被嘲笑。但是从异常消息中阅读链接似乎解释了如何做到这一点:https://learn.microsoft.com/en-us/ef/core/querying/user-definition-function-mapping#mapping-a-method-到自定义 SQL

由于我的 EF 查询在数据库端被转换为带有 ILIKE 函数的 SQL 查询:

SELECT ... FROM ... AS t WHERE ... AND ((t."COLUMN1" ILIKE '%xxx%' ESCAPE '') OR t."COLUMN2" ILIKE '%xxx%' ESCAPE '');

我尝试将其注入上下文中:

modelBuilder.HasDbFunction(typeof(MYDBCONTEXT).GetMethod(nameof(XXX), new[] { typeof(string), typeof(string) })).HasTranslation(args => args[0]);

XXX
下我尝试过
EF.Functions.ILike
但nameof不适用于扩展方法。然后我尝试了
PostgresILikeExpression
,但这实际上是类,所以这里肯定缺少一些语法。而且翻译还没有准备好。

我不确定我是否正确理解了用户定义的函数映射。我可能完全错了,也许问题并不能像我想象的那样得到解决。不过我想向自己保证。也许有人最终实现了我尝试做的事情?

制定诸如模拟整个查询之类的解决方法在我的情况下没有任何意义。在项目的当前状态和可用时间下,切换到数据库本身的测试也是不可能的。

postgresql entity-framework unit-testing mocking user-defined-functions
1个回答
0
投票

我花了大约一个月的时间来解决这个问题。而且解决办法就是这么简单...

您只需要实现内存中搜索并检查静态标志即可调用它。

示例:

public static class UnitTestChecker
{
    public static bool IsTest { get; set; }
}

public static class PostgreSqlExtensions
{
    public static bool ILike(string input, string pattern)
    {
        if (UnitTestChecker.IsTest)
            return InMemoryExtensions.ILike(input, pattern);
        else
            return EF.Functions.ILike(input, pattern);
    }
}

public static class InMemoryExtensions
{
    private static readonly IReadOnlyDictionary<char, string> _patternMapping = new Dictionary<char, string>
    {
        { '%', ".*" },
        { '_', "?*" },
        { '\\', "" },
    };

    public static bool ILike(string input, string postgreSqlPattern)
    {
        var capacity = postgreSqlPattern.Length + postgreSqlPattern.Length / 2; // presumably
        var stringBuilder = new StringBuilder(capacity);

        foreach (var character in postgreSqlPattern)
        {
            if (_patternMapping.TryGetValue(character, out var newCharacters))
                stringBuilder.Append(newCharacters);
            else
                stringBuilder.Append(character);
        }

        var regexPattern = stringBuilder.ToString();
        var regex = new Regex(
            regexPattern,
            options: RegexOptions.IgnoreCase | RegexOptions.CultureInvariant,
            matchTimeout: TimeSpan.FromMilliseconds(50));

        return regex.IsMatch(input);
    }
} 

最后,在单元测试或初始设置中只需设置标志:

if (!UnitTestChecker.IsTest)
    UnitTestChecker.IsTest = true;
© www.soinside.com 2019 - 2024. All rights reserved.