首页 > 解决方案 > 使用实体框架从数据库中检索用户定义的“列组合”

问题描述

我需要使用实体框架从数据库中检索用户定义的列。我需要根据传递的集合创建列投影,一个字符串列表,其中每个元素都包含列名,使用实体框架

我有一个字符串列表,其中包含这样的字符串;

   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 作为参数传递,我的结果应该基于我在 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#conversions

我提到并不得不使用的其他一些材料 - 可能对某人有帮助 - 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());
            }

感谢大家的意见和建议!干杯!

标签: c#entity-frameworklinqpredicatebuilder

解决方案


显然可以在运行时创建类,甚至是新的匿名类型,但它们在代码中的使用方式极为有限。

如果您更喜欢在现代通用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 列类型。


推荐阅读