如何以类型安全的方式编写具有列名作为参数的LINQ查询

RRR*_*RRR 5 c# linq lambda linq-to-entities expression

我正在寻求有关如何使用LINQ以类型安全方式实现此目的的帮助.

我需要在包含许多列的"性能"表上执行搜索.根据为搜索指定的条件,我需要选择列并对具有给定值的列执行搜索.

private static IQueryable<Investment> PerformanceSearch(IQueryable<Investment> investments, **??? searchColumn**, double minValue, double maxValue)
{
  var entity = ExtendedEntities.Current;

  investments = from inv in entity.Investments 
                join performance in entity.Performances on inv.InvestmentID equals perfromance.InvestmentID
                where **performance.searchColumn** >= minValue && **performance.searchColumn** = maxValue
  return investments;
}
Run Code Online (Sandbox Code Playgroud)

现在我正在寻求你的帮助:

  1. 如何以类型安全的方式将列"searchColumn"传递给此方法?我正在考虑创建一个字典对象,以适应某种方式来维护实体框架中的列名.但不知道如何实现这一目标.

  2. 如何使用传递的columnName和应用where子句来执行LINQ查询.

我不能使用If Else或Switch案例如下所示是可能的搜索列表...

/*
 * Search Columns can be:
 *      "Return1Month", "Return2Months", "Return3Months", ... almost 10 more and
 *      "Risk1Month", "Risk2Months", "Risk3Months", ... almost 10 more and
 *      "TrackingError1Month", "TrackingError2Months", "TrackingError3Months", ... almost 10 more and
 *      2 more similar set of columns ... 
 */
Run Code Online (Sandbox Code Playgroud)

我花时间在Stackoverflow,微软和其他博客上,并考虑使用动态LINQ,但它不是类型安全的.它似乎可以使用表达式实现,但无法实现.

任何建议表示赞赏.

编辑 -

另一个要提及的项目 - 所有搜索列都出现在"性能"表中.

mcl*_*129 4

毫无疑问,LINQ 表达式是以强类型方式动态构建 LINQ 查询的最佳方式。您放弃动态 LINQ 库是绝对正确的!LINQ 表达式一开始很难掌握,但我向您保证,最终的回报是值得付出努力的。

下面是一个使用 LINQ 表达式来完成您想要的操作的示例。您会注意到它不包含任何字符串列名称、switch 语句、辅助类或枚举。您需要导入System.Linq.Expressions名称空间才能使其工作:

编辑:该示例现在包括按一个连接表上的列进行过滤,同时从另一个表中选择一个元素。我还investments从方法中删除了参数,因为您实际上不需要传递该参数。您只需直接在方法中访问 EF 表(我将其替换为_performance_investments)。

    public static IQueryable<Investment> PerformanceSearch(Expression<Func<Performance, double>> searchColumn, double minValue, double maxValue) {

        // LINQ Expression that represents the column passed in searchColumn
        // x.Return1Month
        MemberExpression columnExpression = searchColumn.Body as MemberExpression;

        // LINQ Expression to represent the parameter of the lambda you pass in
        // x
        ParameterExpression parameterExpression = (ParameterExpression)columnExpression.Expression;

        // Expressions to represent min and max values
        Expression minValueExpression = Expression.Constant(minValue);
        Expression maxValueExpression = Expression.Constant(maxValue);

        // Expressions to represent the boolean operators
        // x.Return1Month >= minValue
        Expression minComparisonExpression = Expression.GreaterThanOrEqual(columnExpression, minValueExpression);

        // x.Return1Month <= maxValue
        Expression maxComparisonExpression = Expression.LessThanOrEqual(columnExpression, maxValueExpression);

        // (x.Return1Month >= minValue) && (x.Return1Month <= maxValue)
        Expression filterExpression = Expression.AndAlso(minComparisonExpression, maxComparisonExpression);

        // x => (x.Return1Month >= minValue) && (x.Return1Month <= maxValue)
        Expression<Func<Performance, bool>> filterLambdaExpression = Expression.Lambda<Func<Performance, bool>>(filterExpression, parameterExpression);

        // use the completed expression to filter your collection
        // This requires that your collection is an IQueryable.
        // I believe that EF tables are already IQueryable, so you can probably
        // drop the .AsQueryable calls and it will still work fine.
        var query = (from i in _investments
                     join p in _performance.AsQueryable().Where(filterLambdaExpression)
                       on i.InvestmentId equals p.InvestmentId
                     select i);

        return query.AsQueryable();

    } 
Run Code Online (Sandbox Code Playgroud)

您可以PerformanceSearch这样调用,使用这个简单的控制台应用程序作为示例:

    private static IList<Investment> _investments;
    private static IList<Performance> _performance;

    static void Main(string[] args) {

        // Simulate your two Entity Framework tables
        BuildMockDataset();

        // Return1Month is on Performance, but I return IQueryable<Investment>;
        var results = PerformanceSearch(x => x.Return1Month, 300, 1000);

    }
Run Code Online (Sandbox Code Playgroud)

double此示例足够通用,允许您从Performanceas传递属性searchColumn,并将最小值和最大值指定为 as double