tinyint列的生成查询将CAST引入int

时间:2012-01-26 09:47:43

标签: c# sql-server entity-framework

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

查看模型,我的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。

所以我的问题是,有没有办法避免这个CAST超过列tinyint?或者我做错了什么?

提前致谢。

10 个答案:

答案 0 :(得分:11)

如果您将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;

我遇到了同样的问题和blogged about it

答案 1 :(得分:5)

我的同事在Entity Framework 4.0上找到了解决这个问题的非常好的技巧 适用于smallint,我没试过tinyint。

Equals 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!
从我的测试中 - 执行计划在列上完全使用了我的(过滤的)索引。

希望它有所帮助。
施洛米

答案 2 :(得分:2)

CAST会影响性能,因为索引不会在TinyintColumn

上使用

这是"Ten Common SQL Programming Mistakes"中第2点和第4点的组合。 CAST是列上的函数,如果没有它,您无论如何都会出现数据类型不匹配

@p__linq__0应该是tinyint或明确CAST。

然而,根据MS Connect和(SO)asp.net mvc linq sql problem

,LINQ可能不像tinyint主键

你可以&#34;字节&#34;子弹(抱歉)并使用smallint ......

答案 3 :(得分:2)

如果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;

答案 4 :(得分:1)

我正在发布我为此问题采取的解决方案。

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

答案 5 :(得分:1)

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

我刚刚使用EF 4.3 Code First Approach验证,一切顺利。

答案 6 :(得分:1)

在使用带有lambda表达式的EF时遇到了完全相同的问题。将数据类型添加到int不是解决方案,甚至是不好的做法。我发现和其他报道的是,当你采取更笨拙的方法时,你会得到正确的代码,例如:

SomeEntity.FindBy(i =&gt; new List {1} .Contains(i.TinyintColumn))

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

SomeEntity.FindBy(i =&gt; new List {1,2} .Contains(i.TinyintColumn))

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

不要让我开始研究这些行为/反模式对char / nchar数据类型及其对索引的影响。正如我所看到的,集中数据类型系统C#implements周围的一切都是有限的并且会导致重大问题。

我对EF的看法是,对建模良好的表的非常基本的查询转换为错误的SQL代码,EF遵循反模式。鉴于炒作以及EF带来的开发复杂性,我发现这并不令人印象深刻!我现在不会进入那里,因为这将是一个完全不同的讨论!

选择上述任何解决方案,但在使用它们之前要了解其缺点。也许EF版本10会在一定程度上解决问题,但我不会屏住呼吸。

答案 7 :(得分:0)

如果您想保留逻辑,可以使用expression rewrite method。 代码就像     db.MyEntities.Where(e =&gt; e.Id == i).FixIntCast() 并保持应用程序逻辑不变。

答案 8 :(得分:0)

尝试更复杂的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;
        }
    }
}

}

答案 9 :(得分:0)

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