使用 Entity Framework 从数据库中检索用户定义的 'Combination of Columns'
Retrieve user defined 'Combination of Columns' from database with Entity Framework
我需要使用 Entity Framework 从数据库中检索用户定义的列。
我需要根据传递的集合创建列投影,一个字符串列表,其中每个元素包含列名,Entity Framework
我有一个字符串列表,其中包含这样的字符串;
List<string> columnsToSelect = new List<string>();
columnsToSelect.Add("col1 + col2");
columnsToSelect.Add("col2");
columnsToSelect.Add("col3 + col4");
我有一个名为 'RawData' 的 table,有 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
我成功了,我能够在我的投影上应用各种数学条件
并且能够从中创建动态 Class
原文github参考如下:
https://github.com/kahanu/System.Linq.Dynamic
但我发现这里的解释更有用请看这里:
http://ak-dynamic-linq.azurewebsites.net/GettingStarted#conversions
其他一些 material 我提到并不得不使用 - 这可能对某人有帮助 - 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());
}
感谢大家的意见和建议!干杯!
显然可以在运行时创建 类,甚至可以创建新的匿名类型,但它们在代码中的使用方式极为有限。
如果您更喜欢在现代通用 Queryable
框架内工作,并避免在运行时创建具有有限编译时访问权限的 类,您可以推出自己的表达式解析器并构建 Expression
树。诀窍是使用 Array 类型作为 Select
中的 return 以使成员可访问。这确实意味着所有表达式必须 return 相同类型,但此实现会在必要时将所有表达式转换为一种类型。
这是一个示例实现:
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 列类型。
我需要使用 Entity Framework 从数据库中检索用户定义的列。 我需要根据传递的集合创建列投影,一个字符串列表,其中每个元素包含列名,Entity Framework
我有一个字符串列表,其中包含这样的字符串;
List<string> columnsToSelect = new List<string>();
columnsToSelect.Add("col1 + col2");
columnsToSelect.Add("col2");
columnsToSelect.Add("col3 + col4");
我有一个名为 'RawData' 的 table,有 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 我成功了,我能够在我的投影上应用各种数学条件 并且能够从中创建动态 Class
原文github参考如下: https://github.com/kahanu/System.Linq.Dynamic
但我发现这里的解释更有用请看这里: http://ak-dynamic-linq.azurewebsites.net/GettingStarted#conversions
其他一些 material 我提到并不得不使用 - 这可能对某人有帮助 - 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());
}
感谢大家的意见和建议!干杯!
显然可以在运行时创建 类,甚至可以创建新的匿名类型,但它们在代码中的使用方式极为有限。
如果您更喜欢在现代通用 Queryable
框架内工作,并避免在运行时创建具有有限编译时访问权限的 类,您可以推出自己的表达式解析器并构建 Expression
树。诀窍是使用 Array 类型作为 Select
中的 return 以使成员可访问。这确实意味着所有表达式必须 return 相同类型,但此实现会在必要时将所有表达式转换为一种类型。
这是一个示例实现:
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 列类型。