Сгенерированный запрос для столбца tinyint вводит CAST для int - PullRequest
15 голосов
/ 26 января 2012

Я запрашиваю столбец tinyint, и Entity Framework создает запрос SELECT, который вводит CAST в INT для этого столбца, даже если значение, которое я использую в предложении WHERE, имеет тип байта.

При просмотре модели сгенерированный тип для моего столбца tinyint является байтом.

Глядя код:

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? или я что то не так делаю?

Заранее спасибо.

Ответы [ 9 ]

11 голосов
/ 21 января 2013

Если вы используете IList<T>.Contains с List<byte>, Entity Framework не будет разыгрываться.

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

Я столкнулся с той же проблемой и написал об этом в блоге .

5 голосов
/ 12 марта 2012

Мой коллега нашел очень хороший способ преодолеть эту проблему в Entity Framework 4.0.
Работает для smallint, я не примерял tinyint.

Insteal of equals (==) - используйте оператор Contains (), который был реализован в EF 4.0.

Например:
скажем, у вас есть столбец 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 голосов
/ 08 июня 2018

Содержимое решение не может быть оптимизировано БД, если сравнение smallint представляет собой один сегмент фильтрации по нескольким столбцам и существует индекс, соответствующий этим столбцам. Я проверил, что с помощью метода 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;
2 голосов
/ 26 января 2012

CAST повлияет на производительность, потому что индексы не будут использоваться на TinyintColumn

Это комбинация точек 2 и 4 в «Десять распространенных ошибок программирования SQL» . CAST - это функция для столбца, и без нее у вас все равно будет несоответствие типа данных

@p__linq__0 должно быть tinyint или явно CAST.

Однако, может быть, LINQ не любит первичные ключи tinyint согласно MS Connect и (SO) asp.net mvc linq sql проблема

Вы могли бы "байтовать" пулю (извините) и использовать smallint ...

1 голос
/ 07 февраля 2013

Я столкнулся с точно такой же проблемой при использовании EF с лямбда-выражением.Увеличение типа данных до int - это не решение и даже плохая практика.То, что я нашел и как другие сообщили здесь, это то, что вы получаете правильный код, когда вы делаете более неуклюжий подход, например:

SomeEntity.FindBy (i => новый список {1} .Contains (i.TinyintColumn))

Но когда вы столкнетесь с другими проблемами с более чем одним значением для сопоставления.Следующее не будет использовать параметризованные значения запроса, а только встроит их в тело запроса!

SomeEntity.FindBy (i => новый список {1, 2} .Contains (i.TinyintColumn))

Это не так плохо при исходной проблеме, но все же не очень хорошо, так как это означает, что база данных должна составлять план для каждой комбинации значений, которые вы к ней добавляете, и делает анализ производительности практически невозможным, поскольку нет надлежащей агрегациивремени исполнения.Он также имеет некоторые эффекты производительности, которые вы, скорее всего, не увидите в среде с высокой нагрузкой!

Не начинайте меня с того, что такого рода поведения / антишаблоны будут делать с типами данных char / nchar и их влиянием на индексы.На мой взгляд, централизация всего вокруг реализаций системы типов данных C # ограничена и вызывает серьезные проблемы.

Мой взгляд на EF состоит в том, что очень простые запросы к хорошо смоделированным таблицам преобразуются в плохой код SQL, а EF следует-patterns.Это не то, что я нахожу впечатляющим в свете обмана и дополнительной сложности в разработке, которую приносит EF!Сейчас я не буду вдаваться в подробности, поскольку это будет совершенно другое обсуждение!

Выберите любое из приведенных выше решений, но перед тем, как их использовать, узнайте недостатки.Возможно, версия 10 EF решит проблему до некоторой степени, однако я не задерживаю дыхание.

1 голос
/ 24 апреля 2012

Если у вас есть тип данных столбца таблицы Sql tinyint, соответствующие объекты POCO должны иметь свойство типа byte.Это будет работать для вас.В противном случае, когда вы выполняете итерацию по объекту LINQ, он выдаст ошибку, в которой говорится, что невозможно преобразовать тип байта в тип int или что-либо еще, как вы могли определить для свойства.

Я только что проверил с помощью кода EF 4.3Первый подход, все прошло хорошо.

1 голос
/ 03 февраля 2012

Я публикую решение, которое я принял для этой проблемы.

Кажется, что EntityFramework 4.0 всегда генерирует запросы с CAST в полях tinyint или smallint.Поэтому для оптимизации производительности я решил изменить эти поля на INT, чтобы избежать CAST, и я изменил размер других полей nvarchar, которые я все еще могу уменьшить с nvarchar (50) на nvarchar (30).Итак, в конце я изменил размер строки с 143 байт до 135 байт.

0 голосов
/ 24 октября 2014

Попробуйте более сложную версию 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;
        }
    }
}

}

0 голосов
/ 19 мая 2014

Если вы хотите сохранить логику, вы можете использовать метод переписывания выражений . Код будет как db.MyEntities.Where (e => e.Id == i) .FixIntCast () и вы сохраняете логику приложения как есть.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...