5

我正在寻求有关如何以类型安全的方式使用 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;
}

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

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

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

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

/*
 * 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 ... 
 */

我花时间在 Stackoverflow、Microsoft 和其他博客上,并考虑使用 Dynamic LINQ,但它不是类型安全的。似乎使用表达式可以实现,但无法正常工作。

任何建议表示赞赏。

编辑 -

另一个需要提及的项目 - 所有搜索列都存在于“性能”表中。

4

4 回答 4

4

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

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

编辑:该示例现在包括按一个连接表上的列过滤,同时从另一个表中选择一个元素。我还investments从方法中删除了参数,因为您实际上不需要传递它。您只是在方法中直接访问 EF 表(我用_performanceand代替_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();

    } 

您可以这样调用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);

    }

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

于 2012-08-15T03:06:15.673 回答
2

我认为您应该能够仅使用Func<TIn,TOut>参数(在这种情况下不需要表达式)来执行此操作。无论列的类型是什么,都使函数泛型成为类型安全的。这就是我的想法...

private static IQueryable<Investment> PerformanceSearch<TMember>(
                              IQueryable<Investment> investments, 
                              Func<Performance,TMember> SearchColumn, 
                              TMember minValue, 
                              TMember maxValue)
{
    var entity = ExtendedEntities.Current;

    investments = from inv in entity.Investments 
        join perfromance in entity.Performances on inv.InvestmentID equals perfromance.InvestmentID
        where SearchColumn(perfromance) >= minValue && SearchColumn(perfromance) <= maxValue
    return investments;
}

然后你会像这样调用它:

var results = PerformanceSearch<double>(investments, p => p.Return1Month, 10.0, 20.0);
于 2012-08-15T02:51:37.353 回答
1

您可以构建一个包含强类型 where 子句的字典,如下所示:

var wheres = new Dictionary<string, Expression<Func<Performance, bool>>>()
{
    { "Return1Month", p => p.Return1Month >= minValue && p.Return1Month <= minValue },
    { "Return2Months", p => p.Return2Months >= minValue && p.Return2Months <= minValue },
    { "Return3Months", p => p.Return3Months >= minValue && p.Return3Months <= minValue },
    { "Risk1Month", p => p.Risk1Month >= minValue && p.Risk1Month <= minValue },
    { "TrackingError1Month", p => p.TrackingError1Month >= minValue && p.TrackingError1Month <= minValue },
    /* etc */
};

完整的方法如下所示:

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

    var wheres = new Dictionary<string, Expression<Func<Performance, bool>>>()
    {
        { "Return1Month", p => p.Return1Month >= minValue && p.Return1Month <= minValue },
        { "Return2Months", p => p.Return2Months >= minValue && p.Return2Months <= minValue },
        { "Return3Months", p => p.Return3Months >= minValue && p.Return3Months <= minValue },
        { "Risk1Month", p => p.Risk1Month >= minValue && p.Risk1Month <= minValue },
        { "TrackingError1Month", p => p.TrackingError1Month >= minValue && p.TrackingError1Month <= minValue },
        /* etc */
    };

    var investments = (
        from inv in entity.Investments 
        join perfromance in entity.Performances.Where(wheres[searchColumn]) on inv.InvestmentID equals perfromance.InvestmentID
        select inv;

    return investments;
}

与实际的数据库调用相比,为每个调用构建字典的速度非常快,所以不要太担心。如果您确实决定担心,则将字典设为静态私有字段。

于 2012-08-15T02:27:59.423 回答
1
private static IQueryable<Investment> PerformanceSearch(IQueryable<Investment> investments, string searchColumn, double minValue, double maxValue)
{
  var entity = ExtendedEntities.Current;

  investments = from inv in entity.Investments 
                join perfromance in entity.Performances on inv.InvestmentID equals perfromance.InvestmentID
                where
                (
                    (searchColumn = "Return1Month" && perfromance.Return1Month >= minValue && perfromance.Return1Month <= maxValue) ||
                    (searchColumn = "Return2Months" && perfromance.Return2Months >= minValue && perfromance.Return2Months <= maxValue) ||
                    (searchColumn = "Return3Months" && perfromance.Return3Months >= minValue && perfromance.Return3Months <= maxValue) ||
                    (searchColumn = "Risk1Month" && perfromance.Risk1Month >= minValue && perfromance.Risk1Month <= maxValue)
                    // continue like this for as many columns, unless you want to use reflection
                )
  return investments;
}

另一个选项是我们用于动态报告系统的东西,动态代码生成和编译:

http://msdn.microsoft.com/en-us/library/microsoft.csharp.csharpcodeprovider.aspx

于 2012-08-15T02:18:09.777 回答