EntityFramework 比较字符串非常慢,因为创建一个 nvarchar sqlparameter 而不是 varchar
EntityFramework is very slow to compare strings because create a nvarchar sqlparameter instead of varchar
我有这个示例查询:
context.BarcodeTipiDoc.AsQueryable().Where(d => d.Barcode.CompareTo(minBarcode) > 0);
该查询运行非常慢,因为 Entity Framework 为 "minBarcode" 创建 SqlParameter 作为 nvarchar
而不是 varchar
。
我尝试设置列映射:
[Column("Barcode", TypeName = "varchar(21)")]
public string Barcode { get; set; }
但没有任何改变。
有办法告诉 Entity Framework sql 参数的正确类型吗?
这个查询几乎是即时的:
DECLARE @__minBarcode_0 AS Varchar(21)
SET @__minBarcode_0 = 'aa'
SELECT TOP(100) [d].[Barcode], [d].[contenttype], [d].[idvolume], [d].[path_documento], [d].[Progressivo], [d].[Stato]
FROM BarcodeTipiDoc AS [d]
WHERE [d].[Barcode] > @__minBarcode_0
Entity Framework 生成的相同查询,由于 nvarchar:
而需要几分钟
DECLARE @__minBarcode_0 AS nvarchar(21)
SET @__minBarcode_0 = 'aa'
SELECT TOP(100) [d].[Barcode], [d].[contenttype], [d].[idvolume], [d].[path_documento], [d].[Progressivo], [d].[Stato]
FROM BarcodeTipiDoc AS [d]
WHERE [d].[Barcode] > @__minBarcode_0
table 架构:
Barcode varchar(21) Unchecked
tipodoc char(4) Unchecked
codutenteinserimento uniqueidentifier Checked
dataacquisizione datetime Checked
firmato bit Checked
tipodocdescrizione varchar(50) Checked
Stato int Unchecked
originedoc tinyint Checked Unchecked
我不能更改数据库的任何内容,我只需要更改 entity framework.
从 LINQ 生成的 sql 代码
如果没有办法,我将被迫编写并执行 select 作为纯字符串。
版本是entity framework2.2,但我可以升级
在您的列映射中,您最初声明为:
[Column("Barcode", TypeName = "varchar(21)")]
public string Barcode { get; set; }
你能试试这个吗:
[Column(TypeName = "VARCHAR(21)")]
public string Barcode { get; set; }
或者您可以在模型生成器中指定:
modelBuilder.Entity<BarCodeTipiDoc>()
.Property(x=> x.BarCode)
.HasColumnType("varchar(21)");
如果您可以 post 对象的模型 BarcodeTipiDoc
。
也会有所帮助
更新:刚刚看到您正在使用 EF Core。
您可以在上下文的 OnModelCreating
覆盖中将列定义为非 unicode:
modelBuilder.Entity<BarcodeTipiDoc>().Property(x => x.Barcode).IsUnicode(false);
There is a way to tell to Entity Framework the right type of the sqlparameter?
目前(EF Core 2.x, 3.0)没有这样的方式。 EF Core 尝试从表达式内部的用法推断参数类型。
所以 TypeName = "varchar(21)
或 .IsUnicode(false).HasMaxLength(21)
列映射是朝着正确方向迈出的一步。
不幸的是,2.x 参数类型推断对于 ==
、>
等比较运算符成功,但对于 string.CompareTo
、string.Compare
等方法失败。
这已在 3.0 中得到修复,但现在的翻译远非最佳(CASE WHEN ... > 0
而不是简单的 >
)并且还有许多重大更改,因此仅仅升级并不能有道理也有风险。
我能提供的是一个基于类似于 的自定义映射数据库标量方法的解决方案。它引入了几个映射到 string
比较运算符的 string
自定义方法:
public static class StringFunctions
{
public static bool IsGreaterThan(this string left, string right) => string.Compare(left, right) > 0;
public static bool IsGreaterThanOrEqual(this string left, string right) => string.Compare(left, right) >= 0;
public static bool IsLessThan(this string left, string right) => string.Compare(left, right) < 0;
public static bool IsLessThanOrEqual(this string left, string right) => string.Compare(left, right) <= 0;
public static ModelBuilder RegisterStringFunctions(this ModelBuilder modelBuilder) => modelBuilder
.RegisterFunction(nameof(IsGreaterThan), ExpressionType.GreaterThan)
.RegisterFunction(nameof(IsGreaterThanOrEqual), ExpressionType.GreaterThanOrEqual)
.RegisterFunction(nameof(IsLessThan), ExpressionType.LessThan)
.RegisterFunction(nameof(IsLessThanOrEqual), ExpressionType.LessThanOrEqual);
static ModelBuilder RegisterFunction(this ModelBuilder modelBuilder, string name, ExpressionType type)
{
var method = typeof(StringFunctions).GetMethod(name, new[] { typeof(string), typeof(string) });
modelBuilder.HasDbFunction(method).HasTranslation(parameters =>
{
var left = parameters.ElementAt(0);
var right = parameters.ElementAt(1);
// EF Core 2.x
return Expression.MakeBinary(type, left, right, false, method);
});
return modelBuilder;
}
}
对于 EF Core 3.0 替换
return Expression.MakeBinary(type, left, right, false, method);
与(加上相应的 using
s)
if (right is SqlParameterExpression rightParam)
right = rightParam.ApplyTypeMapping(left.TypeMapping);
else if (left is SqlParameterExpression leftParam)
left = leftParam.ApplyTypeMapping(right.TypeMapping);
return new SqlBinaryExpression(type, left, right, typeof(bool), null);
现在您只需拨打
modelBuilder.RegisterStringFunctions();
在您的 OnModelCreating
覆盖中。
然后在您的查询中,而不是
d => d.Barcode.CompareTo(minBarcode) > 0
使用
d => d.Barcode.IsGreaterThan(minBarcode)
它会被翻译成
[d].[Barcode] > @__minBarcode_0
具有正确的数据库参数类型(与 BarCode
列的数据库类型相同)。
我有这个示例查询:
context.BarcodeTipiDoc.AsQueryable().Where(d => d.Barcode.CompareTo(minBarcode) > 0);
该查询运行非常慢,因为 Entity Framework 为 "minBarcode" 创建 SqlParameter 作为 nvarchar
而不是 varchar
。
我尝试设置列映射:
[Column("Barcode", TypeName = "varchar(21)")]
public string Barcode { get; set; }
但没有任何改变。
有办法告诉 Entity Framework sql 参数的正确类型吗?
这个查询几乎是即时的:
DECLARE @__minBarcode_0 AS Varchar(21)
SET @__minBarcode_0 = 'aa'
SELECT TOP(100) [d].[Barcode], [d].[contenttype], [d].[idvolume], [d].[path_documento], [d].[Progressivo], [d].[Stato]
FROM BarcodeTipiDoc AS [d]
WHERE [d].[Barcode] > @__minBarcode_0
Entity Framework 生成的相同查询,由于 nvarchar:
而需要几分钟DECLARE @__minBarcode_0 AS nvarchar(21)
SET @__minBarcode_0 = 'aa'
SELECT TOP(100) [d].[Barcode], [d].[contenttype], [d].[idvolume], [d].[path_documento], [d].[Progressivo], [d].[Stato]
FROM BarcodeTipiDoc AS [d]
WHERE [d].[Barcode] > @__minBarcode_0
table 架构:
Barcode varchar(21) Unchecked
tipodoc char(4) Unchecked
codutenteinserimento uniqueidentifier Checked
dataacquisizione datetime Checked
firmato bit Checked
tipodocdescrizione varchar(50) Checked
Stato int Unchecked
originedoc tinyint Checked Unchecked
我不能更改数据库的任何内容,我只需要更改 entity framework.
从 LINQ 生成的 sql 代码如果没有办法,我将被迫编写并执行 select 作为纯字符串。
版本是entity framework2.2,但我可以升级
在您的列映射中,您最初声明为:
[Column("Barcode", TypeName = "varchar(21)")]
public string Barcode { get; set; }
你能试试这个吗:
[Column(TypeName = "VARCHAR(21)")]
public string Barcode { get; set; }
或者您可以在模型生成器中指定:
modelBuilder.Entity<BarCodeTipiDoc>()
.Property(x=> x.BarCode)
.HasColumnType("varchar(21)");
如果您可以 post 对象的模型 BarcodeTipiDoc
。
更新:刚刚看到您正在使用 EF Core。
您可以在上下文的 OnModelCreating
覆盖中将列定义为非 unicode:
modelBuilder.Entity<BarcodeTipiDoc>().Property(x => x.Barcode).IsUnicode(false);
There is a way to tell to Entity Framework the right type of the sqlparameter?
目前(EF Core 2.x, 3.0)没有这样的方式。 EF Core 尝试从表达式内部的用法推断参数类型。
所以 TypeName = "varchar(21)
或 .IsUnicode(false).HasMaxLength(21)
列映射是朝着正确方向迈出的一步。
不幸的是,2.x 参数类型推断对于 ==
、>
等比较运算符成功,但对于 string.CompareTo
、string.Compare
等方法失败。
这已在 3.0 中得到修复,但现在的翻译远非最佳(CASE WHEN ... > 0
而不是简单的 >
)并且还有许多重大更改,因此仅仅升级并不能有道理也有风险。
我能提供的是一个基于类似于 string
比较运算符的 string
自定义方法:
public static class StringFunctions
{
public static bool IsGreaterThan(this string left, string right) => string.Compare(left, right) > 0;
public static bool IsGreaterThanOrEqual(this string left, string right) => string.Compare(left, right) >= 0;
public static bool IsLessThan(this string left, string right) => string.Compare(left, right) < 0;
public static bool IsLessThanOrEqual(this string left, string right) => string.Compare(left, right) <= 0;
public static ModelBuilder RegisterStringFunctions(this ModelBuilder modelBuilder) => modelBuilder
.RegisterFunction(nameof(IsGreaterThan), ExpressionType.GreaterThan)
.RegisterFunction(nameof(IsGreaterThanOrEqual), ExpressionType.GreaterThanOrEqual)
.RegisterFunction(nameof(IsLessThan), ExpressionType.LessThan)
.RegisterFunction(nameof(IsLessThanOrEqual), ExpressionType.LessThanOrEqual);
static ModelBuilder RegisterFunction(this ModelBuilder modelBuilder, string name, ExpressionType type)
{
var method = typeof(StringFunctions).GetMethod(name, new[] { typeof(string), typeof(string) });
modelBuilder.HasDbFunction(method).HasTranslation(parameters =>
{
var left = parameters.ElementAt(0);
var right = parameters.ElementAt(1);
// EF Core 2.x
return Expression.MakeBinary(type, left, right, false, method);
});
return modelBuilder;
}
}
对于 EF Core 3.0 替换
return Expression.MakeBinary(type, left, right, false, method);
与(加上相应的 using
s)
if (right is SqlParameterExpression rightParam)
right = rightParam.ApplyTypeMapping(left.TypeMapping);
else if (left is SqlParameterExpression leftParam)
left = leftParam.ApplyTypeMapping(right.TypeMapping);
return new SqlBinaryExpression(type, left, right, typeof(bool), null);
现在您只需拨打
modelBuilder.RegisterStringFunctions();
在您的 OnModelCreating
覆盖中。
然后在您的查询中,而不是
d => d.Barcode.CompareTo(minBarcode) > 0
使用
d => d.Barcode.IsGreaterThan(minBarcode)
它会被翻译成
[d].[Barcode] > @__minBarcode_0
具有正确的数据库参数类型(与 BarCode
列的数据库类型相同)。