为tinyint列生成的查询向int引入了CAST

本文关键字:int CAST 查询 tinyint | 更新日期: 2023-09-27 18:21:24

我正在查询一个tinyint列,实体框架生成一个SELECT查询,该查询为该列引入CAST到INT,即使我在WHERE子句中使用的值是字节类型。

查看Model,为我的tinyint列生成的Type是byte。

查看代码:

byte byteValue = 6;
var entityList = from r in rep.DataContext.FooTable
                 where r.TinyintColumn == byteValue
                 select r;

查看生成的查询:

SELECT [Extent1].[TinyintColumn] AS [TinyintColumn] WHERE @p__linq__0 = CAST( [Extent1].[TinyintColumn] AS int) 

我对表演有严格的限制,所以我不希望那些CAST出现在任何选择中。

所以我的问题可能是,有什么办法可以避免这种对专栏的指责吗?还是我做错了什么?

提前谢谢。

为tinyint列生成的查询向int引入了CAST

如果将IList<T>.ContainsList<byte>一起使用,则实体框架不会强制转换。

List<byte> byteValue = new List<byte> { 6 };
var entityList = from r in rep.DataContext.FooTable
             where byteValue.Contains(r.TinyintColumn)
             select r;

我遇到了同样的问题,并在博客上对此发表了看法。

我的同事在EntityFramework4.0上发现了克服这个问题的非常好的技巧。
为smallint工作,我没有试过tinyint。

Insteal of equals(==)-使用EF 4.0实现的Contains()运算符。

例如:
假设您有SmallIntColumn列

而不是:

short shortValue = 6;
var entityList = from r in rep.DataContext.FooTable
                 where r.SmallIntColumn == shortValue
                 select r;

使用

short[] shortValue = new short[] { 6 };
var entityList = from r in rep.DataContext.FooTable
                 where shortValue.Contains(r.SmallIntColumn)
                 select r;

检查生成的SQL-它现在没有CAST
从我的测试来看,执行计划完美地使用了我对列的(过滤的)索引。

希望它能有所帮助
Shlomi

如果smallint比较是多个列上筛选的一个片段,并且有一个索引与这些列匹配,则DB可能不会优化包含的解决方案。我验证了使用Equals方法修复了SmallInt类型的这个问题,至少在EF6上是这样。

代替

short shortValue = 6;
var entityList = from r in rep.DataContext.FooTable
                 where r.SmallIntColumn == shortValue
                 select r;

使用

short shortValue = 6;
var entityList = from r in rep.DataContext.FooTable
                 where r.SmallIntColumn.Equals(shortValue)
                 select r;

CAST将影响性能,因为索引不会在TinyintColumn 上使用

这是"十个常见的SQL编程错误"中第2点和第4点的结合。CAST是列上的一个函数,如果没有它,数据类型将不匹配

@p__linq__0应为tinyint或显式CAST。

然而,根据MS Connect和(SO)asp.net mvc LINQ sql问题,LINQ可能不喜欢tinyint主键

你可以"字节"项目符号(抱歉)并使用smallint。。。

我发布了我为这个问题采取的解决方案。

EntityFramework 4.0似乎总是在tinyint或smallint字段中生成带有CAST的查询。因此,为了优化性能,我决定将这些字段改为INT,以避免CAST,并且我已经更改了其他nvarchar字段的大小,我仍然可以将其从nvarchar(50)减少到nvarchar(30)。最后,我将行的大小从143字节更改为135字节。

如果您的Sql表列数据类型为tinyint,则相应的POCO对象应具有类型为byte的属性。这对你有用。否则,当您迭代LINQ对象时,它将抛出一个错误,说明无法将字节类型转换为int或您为属性定义的任何类型。

我刚刚用EF 4.3代码优先方法进行了验证,一切顺利。

我在使用带lambda表达式的EF时遇到了完全相同的问题。将数据类型转换为int不是一种解决方案,甚至是一种糟糕的做法。我发现,正如其他人在这里报道的那样,当你采取更笨拙的方式时,你确实得到了正确的代码,比如:

SomeEntity.FindBy(i=>新列表{1}。包含(i.TinyintColumn))

但当你遇到其他问题时,你会遇到不止一个值可以匹配的问题。以下内容将不使用参数化的查询值,而只是将它们内联到查询体中!

SomeEntity.FindBy(i=>新列表{1,2}。包含(i.TinyintColumn))

这在最初的问题上并没有那么糟糕,但仍然不好,因为这意味着数据库必须为你向它抛出的每一个值组合制定一个计划,并且由于没有适当的执行时间聚合,几乎不可能进行性能分析。它还有一些性能效果,你宁愿在高负载环境中看不到!

不要让我开始了解这些行为/反模式对char/nchar数据类型的作用及其对索引的影响。在我看来,围绕C#实现的数据类型系统集中一切是有限的,并且会导致重大问题。

我对EF的看法是,对模型良好的表的非常基本的查询会转换为糟糕的SQL代码,EF遵循反模式。鉴于EF带来的炒作和开发的复杂性,我觉得这并不是什么令人印象深刻的事情!我现在不会在这里讨论,因为那将是一个完全不同的讨论!

选择以上任何一种解决方案,但在使用之前要知道缺点。也许EF的第10版会在一定程度上解决这个问题,但我还是屏住呼吸。

如果您想保留逻辑,可以使用表达式重写方法。代码如下数据库。MyEntities。Where(e=>e.Id==i).FixIntCast()并保持应用程序逻辑不变。

尝试更复杂版本的IntCastFixExtension:

namespace System.Linq {

/// <summary>
/// author: Filip Sielimowicz inspired by
/// http://www.entityframework.info/Home/SmallIntProblem
/// </summary>
public static class IntCastFixExtension {
    public static IQueryable<T> FixIntCast<T>(this IQueryable<T> q, bool narrowMemberExpr = true, bool narrowConstantExpr = true) {
        var visitor = new FixIntCastVisitor() {
            narrowConstExpr = narrowConstantExpr,
            narrowMembExpr = narrowMemberExpr
        };
        Expression original = q.Expression;
        var expr = visitor.Visit(original);
        return q.Provider.CreateQuery<T>(expr);
    }
    private class FixIntCastVisitor : ExpressionVisitor {
        public bool narrowConstExpr;
        public bool narrowMembExpr;
        protected override Expression VisitBinary(BinaryExpression node) {
            bool eq = node.NodeType == ExpressionType.Equal;
            bool neq = node.NodeType == ExpressionType.NotEqual;
            if (eq || neq) {
                var leftUncasted = ReducePossiblyNotNecessaryIntCastExpr(node.Left);
                var rightUncasted = ReducePossiblyNotNecessaryIntCastExpr(node.Right);
                var rightConst = node.Right as ConstantExpression;
                if (leftUncasted == null) {
                    return base.VisitBinary(node);
                }
                if (rightUncasted != null) {
                    if (NarrowTypesAreCompatible(leftUncasted.Type, rightUncasted.Type)) {
                        // Usuwamy niepotrzebne casty do intów występujące po obu stronach equalsa
                        return eq ? Expression.Equal(leftUncasted, rightUncasted) : Expression.NotEqual(leftUncasted, rightUncasted);
                    }
                } else if (rightConst != null) {
                    // Zamiast casta argumentu z lewej w górę do inta (tak zrobił linq2entity)
                    // zawężamy występującą po prawej stałą typu 'int' do typu argumentu z lewej
                    if (narrowConstExpr && (rightConst.Type == typeof(int) || rightConst.Type == typeof(int?))) {
                        var value = rightConst.Value;
                        var narrowedValue = value == null ? null : Convert.ChangeType(rightConst.Value, leftUncasted.Type);
                        Expression narrowedConstExpr = Expression.Constant(narrowedValue, leftUncasted.Type);
                        return eq ? Expression.Equal(leftUncasted, narrowedConstExpr) : Expression.NotEqual(leftUncasted, narrowedConstExpr);
                    }
                } else if (node.Right.NodeType == ExpressionType.MemberAccess) {
                    // Jak po prawej mamy wyrażenie odwołujące się do zmiennej typu int to robimy podobnie jak przy stałej
                    // - zawężamy to, zamiast upcasta do inta z lewej.
                    if (narrowMembExpr) {
                        var rightMember = node.Right;
                        var narrowedMemberExpr = Expression.Convert(rightMember, leftUncasted.Type);
                        return eq ? Expression.Equal(leftUncasted, narrowedMemberExpr) : Expression.NotEqual(leftUncasted, narrowedMemberExpr);
                    }
                }
            }
            return base.VisitBinary(node);
        }
        private bool NarrowTypesAreCompatible(Type t1, Type t2) {
            if (t1 == typeof(short?)) t1 = typeof(short);
            if (t2 == typeof(short?)) t2 = typeof(short);
            if (t1 == typeof(byte?)) t1 = typeof(byte);
            if (t2 == typeof(byte?)) t2 = typeof(byte);
            return t1 == t2;
        }
        private bool IsNullable(Type t) {
            return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>);
        }
        private Expression CorrectNullabilityToNewExpression(Expression originalExpr, Expression newExpr) {
            if (IsNullable(originalExpr.Type) == IsNullable(newExpr.Type)) {
                return newExpr;
            } else {
                if (IsNullable(originalExpr.Type)) {
                    Type nullableUncastedType = typeof(Nullable<>).MakeGenericType(newExpr.Type);
                    return Expression.Convert(newExpr, nullableUncastedType);
                } else {
                    Type notNullableUncastedType = Nullable.GetUnderlyingType(newExpr.Type);
                    return Expression.Convert(newExpr, notNullableUncastedType);
                }
            }
        }
        private Expression ReducePossiblyNotNecessaryIntCastExpr(Expression expr) {
            var unnecessaryCast = expr as UnaryExpression;
            if (unnecessaryCast == null ||
                unnecessaryCast.NodeType != ExpressionType.Convert ||
                !(unnecessaryCast.Type == typeof(int) || unnecessaryCast.Type == typeof(int?))
            ) {
                // To nie jest cast na inta, do widzenia
                return null;
            }
            if (
                (unnecessaryCast.Operand.Type == typeof(short) || unnecessaryCast.Operand.Type == typeof(byte)
                || unnecessaryCast.Operand.Type == typeof(short?) || unnecessaryCast.Operand.Type == typeof(byte?))
            ) {
                // Jest cast z shorta na inta
                return CorrectNullabilityToNewExpression(unnecessaryCast, unnecessaryCast.Operand);
            } else {
                var innerUnnecessaryCast = unnecessaryCast.Operand as UnaryExpression;
                if (innerUnnecessaryCast == null ||
                    innerUnnecessaryCast.NodeType != ExpressionType.Convert ||
                    !(innerUnnecessaryCast.Type == typeof(int) || innerUnnecessaryCast.Type == typeof(int?))
                ) {
                    // To nie jest podwójny cast między intami (np. int na int?), do widzenia
                    return null;
                }
                if (
                    (innerUnnecessaryCast.Operand.Type == typeof(short) || innerUnnecessaryCast.Operand.Type == typeof(byte)
                    || innerUnnecessaryCast.Operand.Type == typeof(short?) || innerUnnecessaryCast.Operand.Type == typeof(byte?))
                ) {
                    // Mamy podwójny cast, gdzie w samym środku siedzi short
                    // Robimy skrócenie, żeby intów nie produkował zamiast short -> int -> int?
                    // powinno ostatecznie wychodzić short -> short czyli brak castowania w ogóle.
                    return CorrectNullabilityToNewExpression(unnecessaryCast, innerUnnecessaryCast.Operand);
                }
            }
            return null;
        }
    }
}

}

db列可能为null。试试这个:r.TinyintColumn.Value == byteValue