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
变量。
或者一般来说,客户端类型比较。
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
变量。
或者一般来说,客户端类型比较。