使用System.Linq.Dynamic
命名空间 我能够构造一个通用列列表,以基于当前用户控件中存在的列(我们在各个地方使用的可搜索网格)进行搜索。过程很简单,获取当前用户可见的列列表,将列附加到where子句中的动态查询表达式中,查看整个串联序列是否包含指定的字符串。
这实现了两件事,让用户使用单个搜索框(Google 风格)进行搜索,该搜索框以相同的方式在用户看到的所有网格中工作,以及将搜索卸载到数据库。
目前的工作原理如下(结果 = IQueryable<T
> 或 IEnumerable<T>
):
var se = GetGridSearchExpression(grid);
if (se != null) result = result.Where(se, grid.SearchText.ToLower());
private static string GetGridSearchExpression(Grid grid)
{
if (grid.SearchText.IsNullOrEmpty()) return null;
var sb = new StringBuilder();
foreach (var s in grid.ColumnNames)
{
sb.AppendFormat("{0} {1} ",
sb.Length == 0 ? string.Empty : "+\"|^|\"+", s);
}
return string.Format("({0}).ToLower().Contains(@0)", sb);
}
打印的“|^|”字符串是随机的,以防止单个列上的搜索与下一列匹配,例如列“Bo”“Bryant”与搜索“Bob”匹配,搜索的结果是“Bo |^| Bryant”阻止匹配。
可空值是问题出现的地方,有一个 DateTime?或 Nullable 类型会导致以下错误:
Expression of type 'System.Nullable`1[System.DateTime]' cannot be used for
parameter of type 'System.Object' of method
'System.String Concat(System.Object, System.Object)'
这是 DynamicQueryable 爆炸的部分:
Expression GenerateStringConcat(Expression left, Expression right) {
return Expression.Call(null,
typeof (string).GetMethod("Concat", new[] {typeof (object), typeof (object)}),
new[] {left, right});
}
到目前为止,我发现消除此问题的唯一方法是将表达式生成器中的附加替换为:
foreach (var s in grid.ColumnNames)
{
sb.AppendFormat("{0}({1} != null ? {1}.ToString() : string.Empty)",
sb.Length == 0 ? string.Empty : "+\"|^|\"+", s);
}
由于我们使用的是 LINQ to SQL,因此这会导致 case 语句变得臃肿。考虑到从数据库加载每个对象然后进行搜索的替代方案,最多 8-10 列的 case 语句是可以接受的。
是否有更干净或更简单的方法来完成全部或部分操作?
已编辑:谢谢 Marc...我从不在代码中的任何地方使用 GetEnumerator,总是使用 foreach 或 .ForEach()...但由于当时的某种原因,它使调试变得更容易,不过我现在不记得为什么了。解决了当前代码的问题。
最佳答案
我想知道你是否可以测试 Nullable<T>
并使用条件?但我实际上想知道放弃动态 LINQ 库是否会更好?考虑(未经测试):
string[] columnNames = { "名称", "DoB"}; 字符串查询 =“2008”;
var row = Expression.Parameter(typeof(Data), "row");
Expression body = null;
Expression testVal = Expression.Constant(query, typeof(string));
foreach (string columnName in columnNames)
{
Expression col = Expression.PropertyOrField(row, columnName);
Expression colString = col.Type == typeof(string)
? col : Expression.Call(col, "ToString", null, null);
Expression colTest = Expression.Call(
colString, "Contains", null, testVal);
if (col.Type.IsClass)
{
colTest = Expression.AndAlso(
Expression.NotEqual(
col,
Expression.Constant(null, typeof(string))
),
colTest
);
}
else if (Nullable.GetUnderlyingType(col.Type) != null)
{ // Nullable<T>
colTest = Expression.AndAlso(
Expression.Property(col, "HasValue"),
colTest
);
}
body = body == null ? colTest : Expression.OrElse(body, colTest);
}
Expression<Func<Data, bool>> predicate
= body == null ? x => false : Expression.Lambda<Func<Data, bool>>(
body, row);
var data = new[] {
new Data { Name = "fred2008", DoB = null},
new Data { Name = "barney", DoB = null},
new Data { Name = null, DoB = DateTime.Today},
new Data { Name = null, DoB = new DateTime(2008,1,2)}
};
foreach (Data x in data.AsQueryable().Where(predicate))
{
Console.WriteLine(x.Name + " / " + x.DoB);
}
那么你应该可以使用Where(predicate)
在常规 LINQ 中;请注意,由于空值,这不适用于 LINQ-to-Objects ( IEnumerable<T>
),但在 LINQ-to-SQL 中可能没问题;如果您也需要它在 LINQ-to-Objects 中工作,那很好 - 只需要在上面添加更多详细信息(让我知道)。
关于c# - LINQ-to-SQL 在动态列上搜索?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1457846/