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.CompareTostring.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);

与(加上相应的 usings)

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 列的数据库类型相同)。