【问题标题】:LINQ-to-SQL Search on dynamic columns?LINQ-to-SQL 搜索动态列?
【发布时间】:2009-09-25 22:53:23
【问题描述】:

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

这实现了两件事,让用户使用单个搜索框(谷歌风格)进行搜索,该搜索框在用户看到的所有网格中以相同的方式工作,以及将搜索卸载到数据库。

这是它目前的工作方式(结果 = 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);
}

打印出来的“|^|” 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)'

下面是 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()...但由于某种原因,它在当时让调试变得更容易了,不过我现在不记得为什么了。清除了当前代码的问题。

【问题讨论】:

  • 顺便说一句;我不建议手动展开foreach - 这太容易出错了;例如,您未能处置枚举数。只需使用foreach
  • 编辑显示使用 LINQ-to-Objects

标签: c# linq linq-to-sql


【解决方案1】:

我想知道您是否可以测试Nullable<T> 并使用条件?但我实际上想知道离开动态 LINQ 库是否会更好。考虑(未经测试):

string[] columnNames = { "Name", "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);
        }

那么你应该可以在 regular LINQ 中使用Where(predicate);请注意,由于空值,这不适用于 LINQ-to-Objects (IEnumerable&lt;T&gt;),但在 LINQ-to-SQL 中可能可以;如果您也需要它在 LINQ-to-Objects 中工作,那很好 - 只需在上面添加更多细节(让我知道)。

【讨论】:

  • 它有时有效,但不是在 2 种情况下...与 LoadWith() 连接的子对象抛出不支持的指定方法,否则任何可为空的(IEnumerable 案例)正在抛出一个空引用...网格在几个地方都被连接到一个匿名类型,所以到处都是这些条件的混合体。你的方法可以解决这两个问题吗?如果可能的话,我很想完全放弃 linq.dynamic ...如果您需要更多信息,请告诉我,我会添加到问题中。
  • 我期待您的想法...将搜索功能卸载到数据库中,以应对目前性能最差的情况,这将是一个巨大且受欢迎的改进。
  • 感谢 Marc,这很好用,增加了不区分大小写的功能,非常适合。在这一点上,我已经能够从解决方案中完全删除 System.Linq.Dynamic 并且仍然可以获得我们想要的一切,感谢您的帮助
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-17
  • 1970-01-01
  • 1970-01-01
  • 2011-04-10
相关资源
最近更新 更多