如何有条件地应用 Linq 操作符?

我们正在做一个日志浏览器。用户可以选择根据用户、严重程度等进行过滤。在使用 Sql 的日子里,我会添加查询字符串,但是我想用 Linq 来完成。如何有条件地添加 where-子句?

106905 次浏览

Just use C#'s && operator:

var items = dc.Users.Where(l => l.Date == DateTime.Today && l.Severity == "Critical")

Edit: Ah, need to read more carefully. You wanted to know how to conditionally add additional clauses. In that case, I have no idea. :) What I'd probably do is just prepare several queries, and execute the right one, depending on what I ended up needing.

if you want to only filter if certain criteria is passed, do something like this

var logs = from log in context.Logs
select log;


if (filterBySeverity)
logs = logs.Where(p => p.Severity == severity);


if (filterByUser)
logs = logs.Where(p => p.User == user);

Doing so this way will allow your Expression tree to be exactly what you want. That way the SQL created will be exactly what you need and nothing less.

You could use an external method:

var results =
from rec in GetSomeRecs()
where ConditionalCheck(rec)
select rec;


...


bool ConditionalCheck( typeofRec input ) {
...
}

This would work, but can't be broken down into expression trees, which means Linq to SQL would run the check code against every record.

Alternatively:

var results =
from rec in GetSomeRecs()
where
(!filterBySeverity || rec.Severity == severity) &&
(!filterByUser|| rec.User == user)
select rec;

That might work in expression trees, meaning Linq to SQL would be optimised.

Well, what I thought was you could put the filter conditions into a generic list of Predicates:

    var list = new List<string> { "me", "you", "meyou", "mow" };


var predicates = new List<Predicate<string>>();


predicates.Add(i => i.Contains("me"));
predicates.Add(i => i.EndsWith("w"));


var results = new List<string>();


foreach (var p in predicates)
results.AddRange(from i in list where p.Invoke(i) select i);

That results in a list containing "me", "meyou", and "mow".

You could optimize that by doing the foreach with the predicates in a totally different function that ORs all the predicates.

It isn't the prettiest thing but you can use a lambda expression and pass your conditions optionally. In TSQL I do a lot of the following to make parameters optional:

WHERE Field = @FieldVar OR @FieldVar IS NULL

You could duplicate the same style with a the following lambda (an example of checking authentication):

MyDataContext db = new MyDataContext();

void RunQuery(string param1, string param2, int? param3){

Func checkUser = user =>

((param1.Length > 0)? user.Param1 == param1 : 1 == 1) &&

((param2.Length > 0)? user.Param2 == param2 : 1 == 1) &&

((param3 != null)? user.Param3 == param3 : 1 == 1);

User foundUser = db.Users.SingleOrDefault(checkUser);

}

When it comes to conditional linq, I am very fond of the filters and pipes pattern.
http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/

Basically you create an extension method for each filter case that takes in the IQueryable and a parameter.

public static IQueryable<Type> HasID(this IQueryable<Type> query, long? id)
{
return id.HasValue ? query.Where(o => i.ID.Equals(id.Value)) : query;
}

Another option would be to use something like the PredicateBuilder discussed here. It allows you to write code like the following:

var newKids  = Product.ContainsInDescription ("BlackBerry", "iPhone");


var classics = Product.ContainsInDescription ("Nokia", "Ericsson")
.And (Product.IsSelling());


var query = from p in Data.Products.Where (newKids.Or (classics))
select p;

Note that I've only got this to work with Linq 2 SQL. EntityFramework does not implement Expression.Invoke, which is required for this method to work. I have a question regarding this issue here.

I had a similar requirement recently and eventually found this in he MSDN. CSharp Samples for Visual Studio 2008

The classes included in the DynamicQuery sample of the download allow you to create dynamic queries at runtime in the following format:

var query =
db.Customers.
Where("City = @0 and Orders.Count >= @1", "London", 10).
OrderBy("CompanyName").
Select("new(CompanyName as Name, Phone)");

Using this you can build a query string dynamically at runtime and pass it into the Where() method:

string dynamicQueryString = "City = \"London\" and Order.Count >= 10";
var q = from c in db.Customers.Where(queryString, null)
orderby c.CompanyName
select c;

I ended using an answer similar to Daren's, but with an IQueryable interface:

IQueryable<Log> matches = m_Locator.Logs;


// Users filter
if (usersFilter)
matches = matches.Where(l => l.UserName == comboBoxUsers.Text);


// Severity filter
if (severityFilter)
matches = matches.Where(l => l.Severity == comboBoxSeverity.Text);


Logs = (from log in matches
orderby log.EventTime descending
select log).ToList();

That builds up the query before hitting the database. The command won't run until .ToList() at the end.

If you need to filter base on a List / Array use the following:

    public List<Data> GetData(List<string> Numbers, List<string> Letters)
{
if (Numbers == null)
Numbers = new List<string>();


if (Letters == null)
Letters = new List<string>();


var q = from d in database.table
where (Numbers.Count == 0 || Numbers.Contains(d.Number))
where (Letters.Count == 0 || Letters.Contains(d.Letter))
select new Data
{
Number = d.Number,
Letter = d.Letter,
};
return q.ToList();


}

Doing this:

bool lastNameSearch = true/false; // depending if they want to search by last name,

having this in the where statement:

where (lastNameSearch && name.LastNameSearch == "smith")

means that when the final query is created, if lastNameSearch is false the query will completely omit any SQL for the last name search.

I solved this with an extension method to allow LINQ to be conditionally enabled in the middle of a fluent expression. This removes the need to break up the expression with if statements.

.If() extension method:

public static IQueryable<TSource> If<TSource>(
this IQueryable<TSource> source,
bool condition,
Func<IQueryable<TSource>, IQueryable<TSource>> branch)
{
return condition ? branch(source) : source;
}

This allows you to do this:

return context.Logs
.If(filterBySeverity, q => q.Where(p => p.Severity == severity))
.If(filterByUser, q => q.Where(p => p.User == user))
.ToList();

Here's also an IEnumerable<T> version which will handle most other LINQ expressions:

public static IEnumerable<TSource> If<TSource>(
this IEnumerable<TSource> source,
bool condition,
Func<IEnumerable<TSource>, IEnumerable<TSource>> branch)
{
return condition ? branch(source) : source;
}

You can create and use this extension method

public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool isToExecute, Expression<Func<TSource, bool>> predicate)
{
return isToExecute ? source.Where(predicate) : source;
}