我们可以在 LINQ to SQL 中使用 ToLowerInvariant() 吗?

Can we use ToLowerInvariant() in LINQ to SQL?

我有一种使用 DbContext 和 EF Core 检索餐厅 table 结果的方法,代码如下:

    public IEnumerable<Restaurant> GetRestaurantByName(string? name = null)
    {
        if (string.IsNullOrEmpty(name))
        {
            return _dbContext.Restaurants;
        }
        else
        {
            return _dbContext.Restaurants
                .Where(r => r.Name.ToLower().Contains(name.ToLowerInvariant()));
        }
    }

问题是当我尝试使用 r.Name.ToLowerInvariant() 而不是 r.Name.ToLower() 时,我发现了一个异常,详细信息如下:

InvalidOperationException: The LINQ expression 'DbSet() .Where(r => r.Name.ToLowerInvariant().Contains(__ToLowerInvariant_0))' could not be translated.

我喜欢了解幕后的确切原因和逻辑。

这是我一直苦恼且难以调试的问题。当涉及到在 DB 中使用函数时,where 函数在某种程度上受到限制。

这是一个例子:

DataContext.Products.Where(product => product.Count > 5); \ This works fine
DataContext.Products.Where(product => product.Name == "Mac".ToLower()); \ Also works fine because "Mac" will be converted into "mac" before being inserted in the query, so it's value is handled in the app, not db server, and will be in the query as a static value.
\ Now here let's make a call on the product
DataContext.Products.Where(product => product.Name.ToLowerInvariant() == "mac"); \ Throws an exception because function ToLowerInvariantis not registered in the DB, so the DB doesn't know how to call this function or handle it.

如果您尝试在这些对 DB 起作用的 where 语句中调用函数,也会发生同样的事情

public bool HasItems(Product prod) => prod.Count > 0;
DataContext.Products.Where(prod => HasItems(Prod)); // Also throws the same exception cause the function is again is not mapped to any DB Function.

如果您尝试将 HasItems 设为 Getter 属性 会怎么样?

public class Product {
    public int Count {get;set;}
    public bool HasItems => Count > 0;
}

DataContext.Products.Where(prod => prod.HasItems); 
// Will throw an exception, cause migration won't register HasItems as computed column, so this property is not known in the DB and it doesn't know how to get its value.

因此,只要您尝试调用的函数或列未在数据库服务器中注册,并且查询将在数据库服务器内部运行,它就会抛出异常。但是,如果您对应用程序中可用的数据使用相同的查询,它们将正常工作。

I like to understand the exact reason and logic behind the scene.

EFC是一个翻译器,你需要体会。它会查看您构建的 LINQ 查询并将其一段一段地翻译成 SQL,然后 运行 变成 SQL

当你说:

context.Persons.Where(p => p.Name == "SMITH")

变成:

SELECT * FROM persons WHERE name = 'SMITH'

当你说:

context.Persons.Where(p => p.Name.ToLower() == "SMITH")

变成:

SELECT * FROM persons WHERE LOWER(name) = 'SMITH'

反正这两个功能可能是一样的; C# 区分大小写,但 SQL 服务器可能不区分大小写(默认情况下它使用不区分大小写的排序规则)所以即使 C# 看起来像胡说八道:

Name.ToLower() == "SMITH" //this would never return anything in C#

SQL处于不敏感模式的服务器将 运行 它和 return 结果:

LOWER(name) = 'SMITH' --SQLServer will return

你必须意识到你的 C# 被翻译成另一种语言并且 运行 根据该语言的规则,而不是 C#

如果您想查看您的查询结果,请不要 运行 它:

var q = context.Persons.Where(p => p.Name.ToLower() == "SMITH")
  //.ToList() //don't run it

现在指向调试器中的 q 并检查 DebugView- EFC 会告诉您 SQL 它生成的内容(EFC5+ 功能)


现在请记住我们说过这是一个翻译练习 - EFC 只能翻译 what it knows - 这是针对 sql 服务器的,不同的提供商翻译不同。如果您使用它不知道的函数,例如 ToLowerInvariant,您会收到一个错误。 Microsoft 的任何人(或编写您正在使用的提供程序的人)都没有坐下来想出一种方法,以有意义的方式将 ToLowerInvariant 转换为 SQL。

如果您使 LINQ 过于复杂,您也会遇到错误;你必须找到另一种方法来编写可以翻译的 LINQ,或者抓住机会将(?大量)数据下载到 C# 中并在那里进行处理