【问题标题】:How to combine a linq query dynamically?如何动态组合 linq 查询?
【发布时间】:2013-01-11 11:13:02
【问题描述】:

假设我有一个表,在字符串 (nvarchar) 列中包含格式化值。这些值应该是由一些 const 符号分隔的字符串(让它成为分号;)。例如,

12;0;5;dog //four values separated by a semi-colon

053 //if there is only one value, no semi-colon at the end

分隔符始终是分隔符,它不能是值的一部分。

我需要检查该表中是否已经存在一行,该列中包含一个值列表,其中至少包含一个指定项。换句话说,我有一个值列表:

List<string> list = new List<string>() { "somevalue1", "somevalue2" };

分隔符:

string separator = ";";

我需要编写一个 linq-to-sql 查询来执行此操作:

select ... from sometable
where Value='somevalue1' or Value like 'somevalue1;%' or
      Value like '%;somevalue1' or Value like '%;somevalue1;%'

   or Value='somevalue2' or Value like 'somevalue2;%' or 
      Value like '%;somevalue2' or Value like '%;somevalue2;%'

应该提到的是,任何搜索到的值都可能包含另一个。也就是说,我可能正在搜索 5,而某些行可能包含 1;15;55。这样的行必须​​匹配。而...;5;... 或只是5,或5;...,或...;5 是匹配项。

使用 linq-to sql 我知道如何进行以下类型的查询:

select ... from sometable where (... or ... or ...) and (... or ...) ...

那是

IQueryable<SomeTable> query = dc.SomeTable;
foreach (string l in list)
{
    string s = l;
    query = query.Where(b => b.Value.StartsWith(s + separator) ||
                             b.Value.EndsWith(separator + s) ||
                             b.Value.Contains(separator + s + separator) ||
                             b.Value.Equals(s));
}
if (query.Any()) {/*...*/}

显然Where 语句在生成的 sql 查询中与AND 结合在一起,而我在任何地方都需要OR

那么有没有办法在 C# 代码中获取我需要的查询?或者唯一的方法是使用手写查询和DataContext.ExecuteQuery Method 来做到这一点?

【问题讨论】:

  • 也许你觉得这很有帮助stackoverflow.com/questions/14473380/…
  • 虽然它可能无法更改,但我想指出,此数据未标准化。如果您有一个带有记录 id 和值的单独表而不是分号,这会更容易,从而在记录和您的值之间创建一对多关系,从而利用规范化数据库的功能并使其更容易在 linq 中构造查询。当然,如果你不能修改表结构,那我想你也无能为力了。
  • @LosFrijoles 总的来说我同意你的看法。当然,我必须首先考虑提取一个单独的表。但是,一旦问题已经提出并且可能需要更少的时间然后完全重新设计这个功能,我想知道是否可以使用 linq to sql 构建这种查询。

标签: c# sql sql-server linq linq-to-sql


【解决方案1】:
public static Expression<Func<T, bool>> OrTheseFiltersTogether<T>(
  this IEnumerable<Expression<Func<T, bool>>> filters)
{
    Expression<Func<T, bool>> firstFilter = filters.FirstOrDefault();
    if (firstFilter == null)
    {
        Expression<Func<T, bool>> alwaysTrue = x => true;
        return alwaysTrue;
    }

    var body = firstFilter.Body;
    var param = firstFilter.Parameters.ToArray();
    foreach (var nextFilter in filters.Skip(1))
    {
        var nextBody = Expression.Invoke(nextFilter, param);
        body = Expression.OrElse(body, nextBody);
    }
    Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>(body, param);
    return result;
}

因此,您可以从输入集中轻松构建过滤器列表:

List<string> list = new List<string>() { "somevalue1", "somevalue2" };
List<Expression<Func<SomeTable, bool>>> equalsFilters = list
  .Select(s => row => row.Value == s).ToList();
List<Expression<Func<SomeTable, bool>>> startsWithFilters = list
  .Select(s => row => row.Value.StartsWith(s + ";")).ToList();
List<Expression<Func<SomeTable, bool>>> endsWithFilters = list
  .Select(s => row => row.Value.EndsWith(";" + s).ToList();
List<Expression<Func<SomeTable, bool>>> middleFilters = list
  .Select(s => row => row.Value.Contains(";" + s + ";")).ToList();

Expression<Func<SomeTable, bool>> theFilter = OrTheseFiltersTogether(
  equalsFilters.Concat(startsWithFilters).Concat(endsWithFilters).Concat(middleFilters)
);

query = query.Where(theFilter);

【讨论】:

  • +1:不幸的是,我对表达式不太熟悉。但是我会尝试尝试一下...
  • @Danny Chen、string.StartsWith、string.EndsWith 和 string.Contains 都被 LinqToSql 翻译成适当的“like”表达式。此页面中没有这些方法证实了这一点。 msdn.microsoft.com/en-us/library/bb882672.aspx
【解决方案2】:

我猜UNION 会满足您的需求:

IQueryable<SomeTable> baseQuery = dc.SomeTable;
IQueryable<SomeTable> query = new List<SomeTable>().AsQueryable();
foreach (string l in list)
{
    string s = l;

    query.Union(baseQuery.Where(b => b.Value.StartsWith(s + separator) ||
                             b.Value.EndsWith(separator + s) ||
                             b.Value.Contains(separator + s + separator) ||
                             b.Value.Equals(s)));
}
if (query.Any()) {/*...*/}

【讨论】:

  • 谢谢。这正是我所需要的。
  • 每个列表元素是一次数据库往返吗?或者只是一次数据库往返。我很难说。
  • 由于 query 和 baseQuery 都是 IQueryable 的,数据库往返是在循环之后完成的,此时调用 query.Any()。所以这只是一次往返。
  • 不熟悉linq-to-sql,能不能把string.StartsWith(以及EndsWith,Contains,Equals)翻译成正确的sql语句?
  • 我同意往返是在循环之后完成的。当然,查询根部的空列表不会发送到数据库中,那么 Queryable.Union 是否会变成 Enumerable.Union 和一系列数据库往返?也许最好用 dc.SomeTable.Where(row => 1 == 0);
猜你喜欢
  • 2015-10-11
  • 2010-10-04
  • 2011-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多