Линк дерево для вложенного выражения> - существует (выберите * ...) - PullRequest
0 голосов
/ 07 февраля 2019

атм я пишу модуль общего фильтра в моем приложении.У меня проблема с созданием правильного выражения>.Общий запрос SQL выглядит так:

SELECT distinct ROW_NUMBER
FROM dbo.VIEW_ITEM item
WHERE CODE ='MyName'
  AND EXISTS (SELECT *
              FROM dbo.VIEW_ITEM
              WHERE ROW_NUMBER = item.ROW_NUMBER
                AND CODE='MyName' 
                AND (COL_NUMBER=1 AND DISPLAY='UserName')) 
  AND EXISTS (SELECT * 
              FROM VIEW_ITEM
              WHERE ROW_NUMBER = item.ROW_NUMBER
              AND CODE='MyName'
              AND (COL_NUMBER=3 and DISPLAY='2261'))
ORDER BY ROW_NUMBER

Это (на мой взгляд) лучший способ получить все нужные мне записи.Я не могу использовать опцию соединения, потому что я проверяю в моей И СУЩЕСТВУЕТ ту же таблицу, что и в основном запросе.

Так что мой linq выглядит так:

dboMVI.Where(mvi => mvi.Code == "MyCode")
    .Where(mvi => dboMVI.Where(innerMvi => innerMvi.RowNumber == mvi.RowNumber && innerMvi.Code == "MyCode" && innerMvi.ColNumber == 1 && innerMvi.Display == "UserName").Any())
    .Where(mvi => dboMVI.Where(innerMvi => innerMvi.RowNumber == mvi.RowNumber && innerMvi.Code == "MyCode" && innerMvi.ColNumber == 3 && innerMvi.Display == "2261").Any())
    .Select(mvi => mvi.RowNumber)
    .OrderBy(rn => rn)
    .Distinct();

Это должно вернуть мне все числа строк, которые прошли мою фильтрацию.Мне удалось создать Expression, но я уверен, что есть лучший способ сделать его более универсальным и поместить его в мой модуль фильтрации, который не используется, откуда я передаю его DbContext.

Expression<Func<ViewItem, bool>> filter =mvi =>
 dboMVI.Where(innerMvi => innerMvi.RowNumber == mvi.RowNumber 
&& innerMvi.Code == "MyCode" && innerMvi.ColNumber == 3 && 
innerMvi.Display == "2261").Any()

И для второго фильтра:

Expression<Func<ViewItem, bool>> filter =mvi =>
     dboMVI.Where(innerMvi => innerMvi.RowNumber == mvi.RowNumber 
    && innerMvi.Code == "MyCode" && innerMvi.ColNumber == 1 && 
    innerMvi.Display == "UserName").Any()

Мой вопрос заключается в том, как создать общее дерево выражений для этого запроса LINQ?

Я не смог найти нигде пример создания такого дерева.Я нашел пример передачи аргументов в операторе соединения.Но в моем случае я перехожу с основного номера запроса текущей строки и в подзапросе проверяется, удовлетворяются ли какие-либо условия для этой конкретной строки .


РЕДАКТИРОВАТЬ: после некоторых комментариевя заметил, что сделал ошибку в переписывании запросов с реальных значений на демо.Надеюсь, что теперь это исправлено :) В общем, его рабочее решение просто ищет лучший путь.


EDIT2: В чем моя проблема:

im пытается сгенерировать из LINQ SQLзапрос, который может быть использован EF Core.в моем SQL с использованием AND EXISTS, где в теле я ссылаюсь на ту же таблицу, что и в основном запросе.Что еще в подзапросе я использую ROW_NUMBER из основного запроса.В чем моя проблема?Я не знаю, как создать выражение func (отвечающее за мой подзапрос), потому что я не знаю, как передать в него мой текущий ROW_NUMBER, который я проверяю.Я знаю, как сделать дерево выражений для простых примеров.Но там у меня есть константа в моем теле, например, int или string.Но в этом случае мой const каждый раз меняется на другое значение, поэтому его нельзя жестко запрограммировать.

Ответ

Мне удалось решить эту проблему.Прежде всего пришлось упростить запрос linq.

dboMVI.Where(mvi => mvi.Code == "MyCode")
.Where(mvi => dboMVI.Any(innerMvi => innerMvi.RowNumber == mvi.RowNumber && innerMvi.Code == "MyCode" && innerMvi.ColNumber == 1 && innerMvi.Display == "UserName"))
.Where(mvi => dboMVI.Any(innerMvi => innerMvi.RowNumber == mvi.RowNumber && innerMvi.Code == "MyCode" && innerMvi.ColNumber == 3 && innerMvi.Display == "2261"))
.Select(mvi => mvi.RowNumber)
.OrderBy(rn => rn)
.Distinct()

Чем нужно было создать дерево выражений для элементов внутри любого выражения:

IQueryable<MaterializedViewItem> MyDtoList = Enumerable.Empty<MaterializedViewItem>().AsQueryable();
        var insideProperty = Expression.Parameter(typeof(MaterializedViewItem), "mviAny");
        var baseProperty = Expression.Parameter(typeof(MaterializedViewItem), "mviBaseAny");

        MemberExpression condition0Code = Expression.Property(baseProperty, "MvCode");
        ConstantExpression condition0CodeValue = Expression.Constant("ARAPP");
        var condition0 = Expression.Equal(condition0Code, condition0CodeValue);
        var predicateFirstElement = Expression.Lambda<Func<T, bool>>(condition0, baseProperty);

        MemberExpression conditionACode = Expression.Property(insideProperty, "MvCode");
        ConstantExpression conditionACodeValue = Expression.Constant("MyCode");
        var conditionA = Expression.Equal(conditionACode, conditionACodeValue);

        MemberExpression conditionACol = Expression.Property(insideProperty, "ColNumber");
        ConstantExpression conditionAColValue = Expression.Constant((byte)1);
        var conditionB = Expression.Equal(conditionACol, conditionAColValue);

        MemberExpression conditionDisplay = Expression.Property(insideProperty, "ValueDisplay");
        ConstantExpression conditionDisplayValue = Expression.Constant("UserName");
        var conditionC = Expression.Equal(conditionDisplay, conditionDisplayValue);

        MemberExpression conditionRow = Expression.Property(insideProperty, "RowNumber");
        var newValueToCompare = Expression.PropertyOrField(baseProperty, "RowNumber");
        ConstantExpression conditionRowValue = Expression.Constant(0);
        var conditionD = Expression.Equal(conditionRow, newValueToCompare);

        var condition = Expression.AndAlso(conditionA, conditionB);
        var condition2 = Expression.AndAlso(conditionC, conditionD);
        var condition3 = Expression.AndAlso(condition, condition2);

        var predicate = Expression.Lambda<Func<MaterializedViewItem, bool>>(condition3, insideProperty);

        var callCondtions = BuildAny<MaterializedViewItem>(predicate, MyDtoList.Expression);
        var myPredicate = Expression.Lambda<Func<T, bool>>(callCondtions, baseProperty);

        MemberExpression conditionCol2 = Expression.Property(insideProperty, "ColNumber");
        ConstantExpression conditionCol2Value = Expression.Constant((byte)3);
        var conditionE = Expression.Equal(conditionCol2, conditionCol2Value);

        MemberExpression conditionColDisplay2 = Expression.Property(insideProperty, "ValueDisplay");
        ConstantExpression conditionColDisplay2Value = Expression.Constant("2261");
        var conditionF = Expression.Equal(conditionColDisplay2, conditionColDisplay2Value);
        var condition22 = Expression.AndAlso(conditionA, conditionD);
        var condition23 = Expression.AndAlso(conditionE, conditionF);
        var condition2Final = Expression.AndAlso(condition22, condition23);
        var predicate2 = Expression.Lambda<Func<T, bool>>(condition2Final, insideProperty);

        var callCondtions2 = BuildAny<T>(predicate2, MyDtoList.Expression);

Нужна дополнительная функция, чтобы построить для меня final Any со всемипараметры

public static Expression BuildAny<T>(Expression<Func<T, bool>> predicate, Expression expression)
    {
        var overload = typeof(Queryable).GetMethods().FirstOrDefault(method => method.Name == "Any" && method.GetParameters().Count() == 2);
        var specificMethod = overload.MakeGenericMethod(typeof(T));

        var call = Expression.Call(
            specificMethod,
            expression,
            predicate);

        return call;
    }

Что важно помнить, мы строим IQueryable на основе временного объекта.Позже он должен быть заменен на реальную таблицу БД.Это можно сделать с помощью:

IQueryable<T> queryList = this.DbSet;
        var filtersForDbSet = ExpressionTreeConstantReplacer.CopyAndReplace<DbSet<T>, T>(condition, typeof(EnumerableQuery<T>), this.DbSet);
        class ExpressionTreeConstantReplacer
            {
                internal static Expression<Func<T2, bool>> CopyAndReplace<T, T2>(Expression<Func<T2, bool>> expression, Type originalType, T replacementConstant)
                {
                    var modifier = new ExpressionTreeConstantReplacer<T>(originalType, replacementConstant);
                    var newLambda = modifier.Visit(expression) as LambdaExpression;

                    return Expression.Lambda<Func<T2, bool>>(newLambda.Body, newLambda.Parameters.FirstOrDefault());
                }
        }

и

        class ExpressionTreeConstantReplacer<T> : ExpressionVisitor
    {
        Type originalType;
        T replacementConstant;

        internal ExpressionTreeConstantReplacer(Type originalType, T replacementConstant)
        {
            this.originalType = originalType;
            this.replacementConstant = replacementConstant;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            return c.Type == originalType ? Expression.Constant(replacementConstant) : c;
        }
    }

Если у кого-то возникнет аналогичная проблема в дереве выражений.Запрос строится так же, как и в обычном запросе.Чтобы передать некоторую переменную из основного выражения во внутреннее выражение, вам нужно просто показать, что вы сравниваете их как:

MemberExpression conditionRow = Expression.Property(insideProperty, "RowNumber");
        var newValueToCompare = Expression.PropertyOrField(baseProperty, "RowNumber");
        var conditionD = Expression.Equal(conditionRow, newValueToCompare

1 Ответ

0 голосов
/ 07 февраля 2019

Как я уже упоминал в комментарии к вопросу, ваш запрос может быть упрощен.

[Начальная заметка]

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

[Версия # 1] - первый взгляд

SELECT DISTINCT ROW_NUMBER
FROM dbo.VIEW_ITEM item
WHERE CODE ='MyName'AND COL_NUMBER IN(1, 3) AND DISPLAY IN ('UserName', '2261')
ORDER BY ROW_NUMBER

На основе вашего запроса SQL версия Linq может выглядеть следующим образом:

int[] nums = {1, 3};
string[] disp = {"UserName", "2261"}; 

var result = dboMVI
    .Where(mvi=> mvi.Code == 'MyName' && 
        nums.Any(n=> n.Contains(mvi.ColNumber)) &&
        disp.Any(d=> d.Contains(mvi.Display))
    .OrderBy(x=>x.RowNumber)
    .Select(x=>.RowNumber);

В случае, если приведенный выше запрос не соответствует вашим критериям, вы попытаетесь объединить условия с круглыми скобками:

[Version # 2] - secondlook

SELECT DISTINCT ROW_NUMBER
FROM dbo.VIEW_ITEM item
WHERE (CODE ='MyName'AND COL_NUMBER IN(1, 3)) AND 
    (CODE ='MyName' AND DISPLAY IN ('UserName', '2261'))
ORDER BY ROW_NUMBER

Эквивалент Linq:

var result = dboMVI
    .Where(mvi=> (mvi.Code == 'MyName' && 
        nums.Any(n=> n.Contains(mvi.ColNumber))) &&
        (mvi.Code == 'MyName' && 
           disp.Any(d=> d.Contains(mvi.Display)))
    .OrderBy(x=>x.RowNumber)
    .Select(x=>.RowNumber);

Как видите, для возврата данных должны выполняться оба условия.

[EDIT #2]

Что касается выражения ... я думаю, это должно выглядеть так:

Expression<Func<ViewItem, string, int, string, bool>> filter = (mvi, code, colNo, disp) => 
        dboMVI.Any(innerMvi =>
            innerMvi.RowNumber == mvi.RowNumber  &&
            innerMvi.Code==code && 
            innerMvi.ColNumber == colNo && 
            innerMvi.Display == disp);

[Заключительная записка]

Примечание: я не могу получить доступ к вашим данным и не могу на 100% гарантировать, что вышеуказанный запрос будет соответствовать вашим критериям.

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