EnitityFramework is very slow to compare strings because create a nvarchar sqlparameter instead of varchar

后端 未结 3 1357
别跟我提以往
别跟我提以往 2020-12-11 23:55

I have this sample query:

context.BarcodeTipiDoc.AsQueryable().Where(d => d.Barcode.CompareTo(minBarcode) > 0);
         


        
相关标签:
3条回答
  • 2020-12-12 00:16

    You can define the column as non-unicode in the context's OnModelCreating override:

    modelBuilder.Entity<BarcodeTipiDoc>().Property(x => x.Barcode).IsUnicode(false);
    
    0 讨论(0)
  • 2020-12-12 00:32

    There is a way to tell to Entity Framework the right type of the sqlparameter?

    Currently (EF Core 2.x, 3.0) there is no such way. EF Core tries to infer the parameter type from the usage inside expressions.

    So TypeName = "varchar(21) or .IsUnicode(false).HasMaxLength(21) column mapping is step in the right direction.

    Unfortunately 2.x parameter type inference succeeds for comparison operators like ==, > etc. but fails for methods like string.CompareTo, string.Compare etc.

    This has been fixed in 3.0, but now the translation is far from optimal (CASE WHEN ... > 0 rather than simply >) and also there are many breaking changes, so upgrading just because of that doesn't make sense and is risky.

    What I can offer is a solution based on a custom mapped database scalar methods similar to Entity Framework Core: Guid Greater Than for Paging. It introduces several string custom methods which are mapped to string comparison operators:

    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;
        }
    }
    

    For EF Core 3.0 replace

    return Expression.MakeBinary(type, left, right, false, method);
    

    with (plus respective 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);
    

    Now all you need is to call

    modelBuilder.RegisterStringFunctions();
    

    inside your OnModelCreating override.

    Then inside your query, instead of

    d => d.Barcode.CompareTo(minBarcode) > 0
    

    use

    d => d.Barcode.IsGreaterThan(minBarcode)
    

    and it will be translated to

    [d].[Barcode] > @__minBarcode_0
    

    with correct db parameter type (same as the db type of BarCode column).

    0 讨论(0)
  • 2020-12-12 00:40

    In your column mapping, you originally declared this:

    [Column("Barcode", TypeName = "varchar(21)")]   
    public string Barcode { get; set; }
    

    Can you try this:

    [Column(TypeName = "VARCHAR(21)")]
    public string Barcode { get; set; }
    

    Or you can specify in the Model Builder:

    modelBuilder.Entity<BarCodeTipiDoc>()
                .Property(x=> x.BarCode)
                .HasColumnType("varchar(21)");
    

    It would also help if you could post the model for your object BarcodeTipiDoc.

    Update: Just saw that you were using EF Core.

    0 讨论(0)
提交回复
热议问题