EnitityFramework 比较字符串的速度非常慢,因为创建了 nvarchar sqlparameter 而不是 varchar

Ste*_*tti 3 c# sql-server varchar entity-framework-core .net-core

我有这个示例查询:

context.BarcodeTipiDoc.AsQueryable().Where(d => d.Barcode.CompareTo(minBarcode) > 0);
Run Code Online (Sandbox Code Playgroud)

该查询运行速度非常慢,因为实体框架为“minBarcode”创建了 SqlParameternvarchar而不是varchar.

我试图设置列映射:

[Column("Barcode", TypeName = "varchar(21)")]   
public string Barcode { get; set; }
Run Code Online (Sandbox Code Playgroud)

但什么都没有改变。

有没有办法告诉 Entity Framework sqlparameter 的正确类型?

这个查询几乎是即时的:

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
Run Code Online (Sandbox Code Playgroud)

由实体框架生成的相同查询,由于 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
Run Code Online (Sandbox Code Playgroud)

表架构:

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
Run Code Online (Sandbox Code Playgroud)

我不允许更改数据库的任何内容,我只需要通过实体框架从 LINQ 更改生成的 sql 代码。

如果没有办法,我将被迫编写并将选择作为纯字符串执行。

版本是entity framework 2.2,不过我可以升级。

Iva*_*oev 5

有没有办法告诉 Entity Framework 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而不是简单>)并且还有许多破坏性更改,因此仅因此升级没有意义并且有风险。

我能提供的是一个基于自定义映射数据库标量方法的解决方案,类似于Entity Framework Core: Guid Greater Than for Paging。它引入了几个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;
    }
}
Run Code Online (Sandbox Code Playgroud)

对于 EF Core 3.0 替换

return Expression.MakeBinary(type, left, right, false, method);
Run Code Online (Sandbox Code Playgroud)

与(加上各自的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);
Run Code Online (Sandbox Code Playgroud)

现在你只需要打电话

modelBuilder.RegisterStringFunctions();
Run Code Online (Sandbox Code Playgroud)

在您的OnModelCreating覆盖中。

然后在您的查询中,而不是

d => d.Barcode.CompareTo(minBarcode) > 0
Run Code Online (Sandbox Code Playgroud)

d => d.Barcode.IsGreaterThan(minBarcode)
Run Code Online (Sandbox Code Playgroud)

它会被翻译成

[d].[Barcode] > @__minBarcode_0
Run Code Online (Sandbox Code Playgroud)

具有正确的 db 参数类型(与BarCode列的 db 类型相同)。

  • 哇,SO 的向导在行动!太棒了。 (2认同)