How to combine a linq query dynamically?

2019-08-11 09:58发布

问题:

Suppose I have a table, containing formatted values in a string (nvarchar) column. These values are supposed to be strings separated by some const symbol (let it be a semi-colon ;). For example,

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

or

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

The separator is always a separator, it can't be a part of the value.

I need to check if there is already a row in that table, with a list of values in that column, which contains at least one of the specified items. In other words I have a list of values:

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

the separator:

string separator = ";";

And I need to write a linq-to-sql query doing this:

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;%'

It should be mentioned that any of the searched values may contain the other. That is, I may be searching for exactly 5, while some row may contain 1;15;55. Such row must not be a match. While ...;5;... or just 5, or 5;..., or ...;5 are the matches.

Using linq-to sql I know how to do queries of the following kind:

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

that is

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()) {/*...*/}

Obviously the Where statements are joined with AND in the resulting sql query, while I need OR everywhere.

So is there a way to get the query I need within C# code? Or the only way would be to do this with a hand-written query and DataContext.ExecuteQuery Method?

回答1:

I guess a UNION would suit your needs:

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()) {/*...*/}


回答2:

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;
}

So, you can build a List of filters easily from the input set:

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);