EF Core 3.1 查询中 HasConversion 和 HasMaxLength 映射的不一致行为

EF Core 3.1 Inconsistent behavior of HasConversion and HasMaxLength mappings in queries

EF Core 3.1 在按列过滤时生成低性能查询(因为缺少索引)。

导致问题的列具有以下映射:

builder.Property(p => p.Id)
            .HasConversion(
                value => value.Value,
                dbValue => new LocationId(dbValue)
            )
            .HasMaxLength(50);

LocationId 为值对象

查询由以下代码生成:

_repository.Query.FirstOrDefaultAsync(l => l.Id == "DD212334234");

其中 _repository.Query 基本上是位置的 DbSet。

生成的查询是:

SELECT TOP 1 * FROM [locations] as [l] WHERE CAST([l].[Id] AS nvarchar(max)) = 'DD212334234'

注意对 nvarchar(max) 的 CAST。列 ID 为 nvarchar(50) 并具有索引。 上述针对 250 万条记录的查询执行了大约 1.2 秒。 如果我删除 CAST 和 运行 查询,它将完成 100 毫秒。

如果能帮助解决这个问题,我们将不胜感激。

更新:我的 DbContext 构建于:

builder.UseSqlServer(options.ConnectionString, x =>
                    {
                        x.MigrationsAssembly(assemblyName);
                        x.MigrationsHistoryTable($"__migrations_{contextName}");
                        x.UseNetTopologySuite();
                    });

另一个更新:

如果我注释掉 .HasConversion(...),将生成没有 CAST 的查询。 LocationId是这样的:

public class LocationId : ValueObject
{
    private LocationId(string value)
    {
        Value = value.ToUpperInvariant();
    }

    public string Value { get; }

    public static implicit operator string(LocationId c) => c.Value;
    public static explicit operator LocationId(string s) => new LocationId(s);

     ...
}

问题是运营商造成的

public static implicit operator string(LocationId c) => c.Value;
public static explicit operator LocationId(string s) => new LocationId(s);

表达式

l => l.Id == "DD212334234"

实际上是

l => (string)l.Id == "DD212334234"

这导致翻译后的 CAST SQL。

最初我无法重现它,因为在我的测试中这两个转换都是隐式的

public static implicit operator string(LocationId c) => c.Value;
public static implicit operator LocationId(string s) => new LocationId(s);

所以上面的表达式实际上是

l => l.Id == (LocationId)"DD212334234"

并且在生成的SQL中不引入CAST。

所以解决方案是使运算符 LocationId 隐式,或使用显式转换

l => l.Id == (LocationId)"DD212334234"

LocationId构造函数

l => l.Id == new LocationId("DD212334234")

LocationId变量。

或者一般来说,客户端类型比较。