Извлечение определяемой пользователем 'комбинации столбцов' из базы данных с помощью Entity Framework
Мне нужно получить пользовательские столбцы из базы данных с Entity Framework. Мне нужно создать столбец проекции на основе переданной коллекции, список строк, где каждый элемент содержит имена столбцов, с Entity Framework
У меня есть список строк, который содержит строки, подобные этой;
List<string> columnsToSelect = new List<string>();
columnsToSelect.Add("col1 + col2");
columnsToSelect.Add("col2");
columnsToSelect.Add("col3 + col4");
У меня есть таблица с именем "RawData" с 6 столбцами, например:
Id EmpId col1 col2 col3 col4
Теперь, если бы я запросил простой
var rawDatacolums = Context.RawData.where(a => a.EmpId = @EmpId)
это сгенерирует SQL-оператор, как это,
Select Id,EmpId,col1,col2,col3,col4 from RawData
where EmpId = @EmpId
Здесь я хочу передать columnsToSelect в качестве аргумента, и мой результат должен быть основан на селекторе столбца, который я передаю в списке
Что я хочу сделать, это
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 и заставил его работать, я смог применить различные математические условия к моей проекции и создать из нее Dynamic Class.
Исходная ссылка на 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 ответ
Очевидно, что можно создавать классы во время выполнения или даже новые анонимные типы, но они чрезвычайно ограничены в том, как вы можете использовать их в своем коде.
Если вы предпочитаете работать в рамках современного дженерика 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) типы, допускающие значения NULL, при смешивании значений NULL и NULL.
Учитывая columnsToSelect
Вы предоставили в качестве примера:
List<string> columnsToSelect = new List<string>();
columnsToSelect.Add("col1 + col2");
columnsToSelect.Add("col2");
columnsToSelect.Add("col3 + col4");
Вы можете запросить базу данных так:
var queryResult= Context.RawData.Select(columnsToSelect.SelextExpr<TRawData, int>());
А также queryResult
будет иметь тип IQueryable<int[]>
или же IQueryable<int?[]>
в зависимости от типов столбцов SQL.