Извлечение определяемой пользователем 'комбинации столбцов' из базы данных с помощью Entity Framework - PullRequest
0 голосов
/ 19 января 2019

Мне нужно получить пользовательские столбцы из базы данных с Entity Framework. Мне нужно создать столбец Проекция на основе переданной коллекции, Список строк, где каждый элемент содержит имена столбцов, с Entity Framework

У меня есть список строк, который содержит строки, подобные этой;

   List<string> columnsToSelect = new List<string>();
   columnsToSelect.Add("col1 + col2");
   columnsToSelect.Add("col2");
   columnsToSelect.Add("col3 + col4");

У меня есть таблица с именем «RawData» с 6 столбцами: Идентификатор EmpId col1 col2 col3 col4 RawData Sample Table

Теперь, если бы я запросил простой

var rawDatacolums = Context.RawData.where(a => a.EmpId = @EmpId)

это сгенерирует оператор SQL следующим образом:

Select Id,EmpId,col1,col2,col3,col4 from  RawData
where EmpId = @EmpId

Здесь я хочу передать columnsToSelect в качестве аргумента, и мой результат должен основываться на селекторе столбцов, который я передаю в List

Что я хочу сделать, это

var rawDatacolums = Context.RawData.select(columnsToSelect).where(a => 
a.EmpId = @EmpId)

Который должен генерировать SQL как;

Select col1 + col2 as Col1Col2, col2 as Col2, col3 + col4 as Col3Col4 
from  RawData where EmpId = @EmpId

Я пытался использовать «SelectProperties» из этой статьи здесь:

https://byalexblog.net/entity-framework-dynamic-columns https://github.com/lAnubisl/entity-framework-dynamic-queries/blob/master/entity-framework-dynamic-queries/SelectiveQuery.cs

var rawDatacolums = Context.RawData.SelectProperties(columnsToSelect) 

если передать точные столбцы, такие как col1, col2 как список, он работает но это не работает так, как я хочу, например, сумма двух столбцов

Мое требование - мне нужно спроектировать добавление столбцов как 'col1 + col2' & 'col3 + col4'

Обновленный ответ

Основываясь на нескольких предложениях, я больше играл с Dynamic LINQ и я заставил это работать, я смог применить различные математические условия к моей проекции и смог создать Динамический Класс из него

Исходная ссылка на github приведена ниже: https://github.com/kahanu/System.Linq.Dynamic

но я нашел объяснение здесь более полезным, пожалуйста, посмотрите здесь: http://ak -dynamic-linq.azurewebsites.net / GettingStarted # преобразования

Некоторые другие материалы, на которые я ссылался и которые нужно было использовать - которые могут быть полезны для кого-то здесь - http://www.albahari.com/nutshell/predicatebuilder.aspx

Пример рабочего кода будет выглядеть так:

            var sampleQuery = "New(Col1+Col2 as Stage1Count)";
            IEnumerable queryResult= Context.RawData.AsQueryable().Select(sampleQuery );


            System.Diagnostics.Debug.WriteLine("Debug Sample Query: " + queryResult.ToString());
            foreach (var cust in queryResult)
            {
                System.Diagnostics.Debug.WriteLine("Debug Sample StageCount : " + cust.ToString());
            }

Спасибо всем за ваши комментарии и предложения! Ура!

1 Ответ

0 голосов
/ 22 января 2019

Очевидно, что можно создавать классы во время выполнения или даже новые анонимные типы, но они чрезвычайно ограничены в том, как вы можете использовать их в своем коде.

Если вы предпочитаете работать в современной универсальной инфраструктуре Queryable и избегать создания классов во время выполнения, которые имеют ограниченный доступ во время компиляции, вы можете свернуть свой собственный анализатор выражений и построить деревья Expression. Хитрость заключается в том, чтобы использовать тип Array в качестве возврата из Select, чтобы сделать члены доступными. Это означает, что все выражения должны возвращать один и тот же тип, но эта реализация при необходимости преобразует все выражения в один тип.

Вот пример реализации:

public static class IQueryableExt {
    public static Expression<Func<TRec, TVal?[]>> SelectExpr<TRec, TVal>(this IEnumerable<string> strExprs) where TVal : struct {
        var p = Expression.Parameter(typeof(TRec), "p");
        var exprs = strExprs.Select(se => {
            var e = se.ParseExpression(p);
            return e.Type.IsNullableType() && e.Type.GetGenericArguments()[0] == typeof(TVal) ? e : Expression.Convert(e, typeof(TVal?));
        }).ToArray();

        return Expression.Lambda<Func<TRec, TVal?[]>>(Expression.NewArrayInit(typeof(TVal?), exprs), p);
    }

    static char[] operators = { '+', '-', '*', '/' };
    static Regex tokenRE = new Regex($@"(?=[-+*/()])|(?<=[-+*/()])", RegexOptions.Compiled);
    static HashSet<char> hsOperators = operators.ToHashSet();
    static Dictionary<char, ExpressionType> opType = new Dictionary<char, ExpressionType>() {
        { '*', ExpressionType.Multiply },
        { '/', ExpressionType.Divide },
        { '+', ExpressionType.Add },
        { '-', ExpressionType.Subtract }
    };

    static int opPriority(char op) => hsOperators.Contains(op) ? Array.IndexOf(operators, op) >> 1 : (op == ')' ? -1 : -2);

    public static Expression ParseExpression(this string expr, ParameterExpression dbParam) {
        var opStack = new Stack<char>();
        opStack.Push('(');
        var operandStack = new Stack<Expression>();

        foreach (var t in tokenRE.Split(expr).Where(t => !String.IsNullOrEmpty(t)).Append(")")) {
            if (t.Length > 1) // process column name
                operandStack.Push(Expression.PropertyOrField(dbParam, t));
            else {
                while (t[0] != '(' && opPriority(opStack.Peek()) >= opPriority(t[0])) {
                    var curOp = opStack.Pop();
                    var right = operandStack.Pop();
                    var left = operandStack.Pop();
                    if (right.Type != left.Type) {
                        if (right.Type.IsNullableType())
                            left = Expression.Convert(left, right.Type);
                        else if (left.Type.IsNullableType())
                            right = Expression.Convert(right, left.Type);
                        else
                            throw new Exception($"Incompatible types for operator{curOp}: {left.Type.Name}, {right.Type.Name}");
                    }
                    operandStack.Push(Expression.MakeBinary(opType[curOp], left, right));
                }
                if (t[0] != ')')
                    opStack.Push(t[0]);
                else
                    opStack.Pop(); // pop (
            }
        }
        return operandStack.Pop();
    }

    public static bool IsNullableType(this Type nullableType) =>
    // instantiated generic type only                
        nullableType.IsGenericType &&
        !nullableType.IsGenericTypeDefinition &&
        Object.ReferenceEquals(nullableType.GetGenericTypeDefinition(), typeof(Nullable<>));
}

К сожалению, вывод типа не может легко получить тип ответа, поэтому вы должны вручную передать тип записи и тип ответа. Обратите внимание, что в синтаксическом анализаторе есть специальный код для обработки преобразования в (распространенные в SQL) обнуляемые типы при смешивании обнуляемых и не обнуляемых типов.

Учитывая columnsToSelect, который вы предоставили в качестве примера:

List<string> columnsToSelect = new List<string>();
columnsToSelect.Add("col1 + col2");
columnsToSelect.Add("col2");
columnsToSelect.Add("col3 + col4");

Вы можете запросить базу данных следующим образом:

var queryResult= Context.RawData.Select(columnsToSelect.SelectExpr<TRawData, int>());

И queryResult будет иметь тип IQueryable<int[]> или IQueryable<int?[]> в зависимости от типов столбцов SQL.

...