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

时间:2012-08-15 02:08:53

标签: 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;
}

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

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

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

  3. 我无法使用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 ... 
     */
    

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

    感谢任何建议。

    编辑 -

    需要提及的另一项 - 所有搜索列都出现在“效果”表中。

4 个答案:

答案 0 :(得分:4)

请注意,LINQ表达式是以强类型方式动态构建LINQ查询的最佳方式。您放弃动态LINQ库是绝对正确的! LINQ表达式一开始很难掌握,但我向你保证,最终的回报是非常值得的。

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

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

_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); } 属性从double传递为Performance,将最小值和最大值指定为searchColumn

答案 1 :(得分:2)

我认为你应该只使用 Func&lt; TIn,TOut&gt; 参数(在这种情况下不需要的表达式)来做到这一点。无论列的类型如何,使函数通用都是类型安全的。这就是我在想的......

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);

答案 2 :(得分: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

答案 3 :(得分: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;
}

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