Подтвердить что ты не робот

Сгенерированный запрос для столбца tinyint представляет CAST для int

Я запрашиваю столбец 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? или я делаю что-то неправильно?

Спасибо заранее.

4b9b3361

Ответ 1

Если вы используете 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;

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

Ответ 2

Мой коллега нашел очень хороший трюк, чтобы преодолеть эту проблему на 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!
И из моих тестов - план выполнения использовал мой (фильтрованный) индекс в столбце просто отлично.

Надеюсь, что это помогло. Шломи

Ответ 3

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

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

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

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

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

Ответ 4

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

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

Ответ 5

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

Я только что проверил с помощью EF 4.3 Code First Approach, все прошло хорошо.

Ответ 6

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

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

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

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

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

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

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

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

Ответ 7

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

Ответ 8

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

}