LINQ-to-SQL在动态列上搜索?

Nic*_*ver 2 c# linq linq-to-sql

使用System.Linq.Dynamic命名空间我能够构建一个通用列列表,以根据当前用户控件中存在的列进行搜索(我们在各个地方使用的可搜索网格).过程很简单,将列列表显示给当前用户,将列附加到where子句中的动态查询表达式中,查看整个连接序列是否包含指定的字符串.

这实现了两件事,让用户使用单个搜索框(谷歌样式)进行搜索,该搜索框适用于用户以相同方式查看的所有网格,以及将搜索卸载到数据库.

以下是它当前的工作原理(result = 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);
}
Run Code Online (Sandbox Code Playgroud)

印有"| ^ |" string是随机的,以防止单个列上的搜索与下一个匹配,例如列"Bo""Bryant"匹配搜索"Bob",搜索的结果是"Bo | ^ | Bryant"阻止匹配.

Nullables是问题的来源,有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)'
Run Code Online (Sandbox Code Playgroud)

这是DynamicQueryable的一部分,它正在爆炸:

Expression GenerateStringConcat(Expression left, Expression right) {
  return Expression.Call(null,
    typeof (string).GetMethod("Concat", new[] {typeof (object), typeof (object)}),
    new[] {left, right});
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,我发现消除此问题的唯一方法是将表达式构建器中的append替换为:

  foreach (var s in grid.ColumnNames)
  {
    sb.AppendFormat("{0}({1} != null ? {1}.ToString() : string.Empty)", 
      sb.Length == 0 ? string.Empty : "+\"|^|\"+", s);
  }
Run Code Online (Sandbox Code Playgroud)

由于我们处于LINQ to SQL,因此会导致一个膨胀的case语句.给定从DB 加载每个对象然后搜索的替代方案,最多8-10列的case语句是可接受的.

是否有更清洁或更简单的方法来完成全部或部分内容?

编辑:谢谢Marc ...我从来没有在我的代码中的任何地方使用GetEnumerator,总是foreach或.ForEach()...但由于某种原因,它使调试更容易,但我现在不记得为什么.清除了当前代码的问题.

Mar*_*ell 5

我想知道你是否可以测试Nullable<T>并使用条件?但我真的想知道离开Dynamic LINQ Library会更好; 考虑(未经测试):

string [] columnNames = {"Name","DoB"}; string query ="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);
        }
Run Code Online (Sandbox Code Playgroud)

然后你应该能够Where(predicate)常规的 LINQ中使用; 注意,这不会与LINQ到对象(工作IEnumerable<T>)由于空,但很可能是在OK LINQ到SQL; 如果你需要它在LINQ-to-Objects中也可以正常工作 - 只需要在上面添加一些细节(让我知道).