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

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

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

private static IQueryable PerformanceSearch(IQueryable 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”传递给此方法? 我正在考虑创建一个字典对象,以适应某种方式来维护entity framework中的列名。 但不知道如何实现这一目标。

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

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

任何建议表示赞赏。

编辑 –

另一个要提及的项目 – 所有搜索列都出现在“性能”表中。

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

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

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

  public static IQueryable PerformanceSearch(Expression> 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> filterLambdaExpression = Expression.Lambda>(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 _investments; private static IList _performance; static void Main(string[] args) { // Simulate your two Entity Framework tables BuildMockDataset(); // Return1Month is on Performance, but I return IQueryable; var results = PerformanceSearch(x => x.Return1Month, 300, 1000); } 

此示例足够通用,允许您从Performance传递double属性作为searchColumn ,将min和max值指定为double

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

 private static IQueryable PerformanceSearch( IQueryable investments, Func 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(investments, p => p.Return1Month, 10.0, 20.0); 
 private static IQueryable PerformanceSearch(IQueryable 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

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

 var wheres = new Dictionary>>() { { "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 PerformanceSearch(IQueryable investments, string searchColumn, double minValue, double maxValue) { var entity = ExtendedEntities.Current; var wheres = new Dictionary>>() { { "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; } 

与实际的数据库调用相比,为每个调用构建字典非常快,所以不要过于担心它。 如果你决定担心,那么使字典成为一个静态私有字段。