I've figured out how to do conditional queries with linq to sql and I've also figured out how to OR where clauses. Unfortunately I can't figure out how to do both at once. I can do a conditional where clause something like:
var ResultsFromProfiles = from AllPeeps in SearchDC.aspnet_Users
select AllPeeps;
if (SearchFirstNameBox.Checked)
{
ResultsFromProfiles = ResultsFromProfiles.Where(p => p.tblUserProfile.FirstName.Contains(SearchTerm));
}
if (SearchLastNameBox.Checked)
{
ResultsFromProfiles = ResultsFromProfiles.Where(p => p.tblUserProfile.LastName.Contains(SearchTerm));
}
This will get me any profiles where the first name AND the last name contain the search term.
Or I could do:
var ResultsFromProfiles = from p in SearchDC.aspnet_Users
where p.tblUserProfile.LastName.Contains(SearchTerm) ||
p.tblUserProfile.FirstName.Contains(SearchTerm)
select p;
This would get me any profiles where the first name OR the last name contains the search term.
I have a bunch of checkboxes where the user can specify which fields they want to search for teh search term, so I want to be able to build a query that will conditionally add them as in the first code snippet above, but add them as an OR so they work like the second snippet. That way it will search for any matches anywhere in the specified fields.
Any tips?
Yeah, use PredicateBuilder. It lets you build up dynamic queries with And or Or semantics. It's free and stable- I use it all over the place.
One way to do this is to manipulate the LINQ expression tree for your query. In your case, you'd need to build a lambda expression and substitute it in for your call to Where. Here is a working example based on a list, but the code to manipulate the expression tree is the same regardless of the query provider.
List<User> Users = new List<User>();
Users.Add(new User() { FirstName = "John", LastName = "Smith" });
Users.Add(new User() { FirstName = "Jane", LastName = "Smith" });
string Query = "John";
var Queryable = Users.AsQueryable();
var Results = (from u in Queryable
select u);
//initial method call... the lambda u => false is a place-holder that is about to be replaced
MethodCallExpression WhereExpression = (MethodCallExpression)Results.Where(u => false).Expression;
//define search options
Expression<Func<User, string, bool>> FilterLastName = (u, query) => u.LastName.Contains(query);
Expression<Func<User, string, bool>> FilterFirstName = (u, query) => u.FirstName.Contains(query);
//build a lambda based on selected search options... tie the u parameter to UserParameter and the query parameter to our Query constant
ParameterExpression UserParameter = Expression.Parameter(typeof(User), "u");
Expression Predicate = Expression.Constant(false); //for simplicity, since we're or-ing, we'll start off with false || ...
//if (condition for filtering by last name)
{
Predicate = Expression.Or(Predicate, Expression.Invoke(FilterLastName, UserParameter, Expression.Constant(Query)));
}
//if (condition for filtering by first name)
{
Predicate = Expression.Or(Predicate, Expression.Invoke(FilterFirstName, UserParameter, Expression.Constant(Query)));
}
//final method call... lambda u => false is the second parameter, and is replaced with a new lambda based on the predicate we just constructed
WhereExpression = Expression.Call(WhereExpression.Object, WhereExpression.Method, WhereExpression.Arguments[0], Expression.Lambda(Predicate, UserParameter));
//get a new IQueryable for our new expression
Results = Results.Provider.CreateQuery<User>(WhereExpression);
//enumerate results as usual
foreach (User u in Results)
{
Console.WriteLine("{0} {1}", u.FirstName, u.LastName);
}
Working with expression trees can typically be simplified by using the visitor pattern, but I've omitted that so you could more clearly see the work that has to be done.
Here's a suggestion.
I haven't tried compiling and running it, and LinqToSQL is full of surprises, so no guarantees :-)
var ResultsFromProfiles = from AllPeeps in SearchDC.aspnet_Users select AllPeeps;
IEnumerable<AspNet_User> total = new AspNew_User[0];
if (SearchFirstNameBox.Checked)
{
total = total.Concat(ResultsFromProfiles.Where(p => p.tblUserProfile.FirstName.Contains(SearchTerm));}
}
if (SearchLastNameBox.Checked)
{
total = total.Concat(ResultsFromProfiles.Where(p => p.tblUserProfile.LastName.Contains(SearchTerm));
}
total = total.Distinct();
Related
I have a LINQ statement that I need to do a "contains" with, but also need some sort of loop.
The format of the data is as follows:
x.Product_Name = "product[x], product[y], product[z]"
As user selects multiple items from a list to search on.
I need to find anything within Product_Name that was selected from the user.
var names = JsonConvert.DeserializeObject<IEnumerable<string>>
(criteria.value).ToArray();
This line gets the items a user selected from the list and stores them in an array.
query = query.Where(x => names.contains(x.Product_Name))
Doesn't work because Product_Name is a flattened out version of products, so I can't do this.
What I need is something like the following:
foreach (string s in names)
{
projectsQuery = projectsQuery.Where(x => x.Product_Name.Contains(s));
}
But when the SQL is created for the above, it uses an AND conditional instead of an OR conditional. I need to find any instances where string s is contained within the Product_Name.
You can achieve it by creating Expression tree manually. Although it is kind of hard to manage code.
var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var xParameter = Expression.Parameter(typeof(x), "x");
var searchexpression = new stack<expression>();
foreach (string s in names)
{
var containsmethodexp = expression.call(Expression.Property(xParameter, "Product_Name", containsMethod, expression.constant(s));
if (searchexpression.count == 0)
{
searchexpression.push(containsmethodexp);
}
else
{
searchexpression.push(expression.orelse(containsmethodexp, searchexpression.pop()));
}
}
var finalResult = projectsQuery.Where(Expression.Lambda<Func<x, bool>>(searchexpression.pop(), new ParameterExpression[] { xParameter }));
here x is your Entity Name
I have a method in my data layer in which I pass in a set of search parameters, dynamically build up the required 'where' clause using the PredicateBuilder class from LINQKit and then execute the LINQ query to return a list of objects.
In some cases the search criteria may contain a category name which doesn't exist in the Library table, I then need to join to this table to perform the necessary query. The reason it's been done this way is because there are potentially hundreds of categories per book and for optimisation reasons I only want the join to be performed if actually required.
Now my question is, is it possible to dynamically append this join to the LINQ query?
After a few attempts at trying to get this to work I've unfortunately had to resort to the 'cut-and-paste' pattern which I dislike immensely but needed something that worked for it to get 'out-the-door'.
The code below is an extract with variables renamed of what I've currently got (i.e. this isn't really a library application!):
public IEnumerable<ILibrarySearchResultsDTO> SearchLibrary(ISearchLibrary searchValues)
{
var whereStatement = PredicateBuilder.True<Library>();
bool categorySearch = false;
if (!string.IsNullOrEmpty(searchValues.AuthorFirstName))
whereStatement = whereStatement.And(q => q.AuthorFirstName == searchValues.AuthorFirstName);
if (!string.IsNullOrEmpty(searchValues.AuthorLastName))
whereStatement = whereStatement.And(q => q.AuthorLastName == searchValues.AuthorLastName);
if (!string.IsNullOrEmpty(searchValues.CategoryName))
categorySearch = true;
var libraryObjectSet = Context.CreateObjectSet<Library>();
LibraryObjectSet.MergeOption = MergeOption.NoTracking;
var categoriesObjectSet = Context.CreateObjectSet<Categories>();
categoriesObjectSet.MergeOption = MergeOption.NoTracking;
if (!categorySearch)
{
var query = from lib in libraryObjectSet
.Where(whereStatement)
.Take(ConfigHelper.MaxQueryRecords)
.AsExpandable()
select new LibrarySearchResultsDTO()
{
BookName = lib.BookName,
AuthorFirstName = lib.AuthorFirstName,
AuthorLastName = lib.AuthorLastName,
ISBN = lib.ISBN
};
}
else
{
var query = from lib in LibraryObjectSet
.Where(whereStatement)
.Take(ConfigHelper.MaxQueryRecords)
.AsExpandable()
join categories_LKP in categoriesObjectSet on new { CategoryID = lib.CategoryID, CategoryName = searchValues.CategoryName }
equals new { CategoryID = categories_LKP.CategoryID, CategoryName = categories_LKP.CategoryName }
select new LibrarySearchResultsDTO()
{
BookName = lib.BookName,
AuthorFirstName = lib.AuthorFirstName,
AuthorLastName = lib.AuthorLastName,
ISBN = lib.ISBN
};
}
return query.ToList();
}
I've had to create the sample code in Notepad++ and because it's a contrived example I haven't been able to check if it compiles. Should do though (I hope!).
If there is a navigation property from Library to Category you can just dynamically add another predicate:
if (!string.IsNullOrEmpty(searchValues.CategoryName))
{
whereStatement = whereStatement
.And(q => q.Categories
.Any(c => c.CategoryName == searchValues.CategoryName));
}
If the navigation property is not present, you can still use a join in the predicate without having to duplicate the whole query. But it could be a good reason to add a navigation property.
You may use Reflection API like a following generic function...which compiles a dynamic query with a unknown type...
IQueryable<T> getQuery<T>(T myTableEntity, string[] arrayOfQueryTerms, Expression<Func<T, bool>> predicate)
{
var fieldOrProperty = getMemberInfo(predicate);
}
MemberInfo getmemberInfo<T>(Expression<Func<T,bool> expr)
{
var memberExpr = expr as MemberExpression;
if (memberExpr != null) return memberExpr.Member;
throw new ArgumentException();
}
var q = getQuery<FooTable>(foo, new[]{"Bar","Baz"}, x=>x.FieldName);
How do you write a dynamic Linq query for the following simple search criteria?
1) StudentNumber
2) LastName
3) LastName and FirstName
//if (!String.IsNullOrEmpty(StudentNumber))
var results = (from s in Students
where s.StudentNumber == 1001
select s
);
//else if (!String.IsNullOrEmpty(LastName) & (String.IsNullOrEmpty(FirstName))
var results = (from s in Students
where s.LastName == "Tucker"
select s
);
//else if (!String.IsNullOrEmpty(LastName) & (!String.IsNullOrEmpty(FirstName))
var results = (from s in Students
where s.LastName == "Tucker" && s.FirstName == "Ron"
select s
);
You need to declare your results variable outside of any individual query. This will allow you append different filters based upon your varying criteria, and append as many filters as you need. An example:
var results = Students.AsEnumerable(); // use .AsQueryable() for EF or Linq-to-SQL
if (!string.IsNullorEmpty(StudentNumber))
{
results = results.Where(s => s.StudentNumber.Equals(StudentNumber));
}
else if (!string.IsNullOrEmpty(LastName))
{
results = results.Where(s => s.LastName.Equals(LastName));
if (!string.IsNullOrEmpty(FirstName))
{
results = results.Where(s => s.FirstName.Equals(FirstName));
// filter is in addition to predicate against LastName
}
}
// results can be used here
If dealing with Linq-to-Entities or -Sql, type the initial query with Students.AsQueryable(); so that the filtering happens at the database rather than inside the application.
Is there a way I can construct the WHERE clause first and use it in a
Linq query without if...else
If you want to build the entire where before the first step of the query, it's the same logic. You are conditionally building the predicate, so you will have some sort of if/else involved. However, to build the entire predicate first, you could build against a Func<Student, bool> for Linq to Objects.
Func<Student, bool> predicate;
if (!string.IsNullOrEmpty(StudentNumber))
{
predicate = s => s.StudentNumber.Equals(StudentNumber);
}
else if (!string.IsNullOrEmpty(LastName))
{
predicate = s => s.LastName.Equals(LastName);
if (!string.IsNullOrEmpty(FirstName))
{
Func<Student, bool> p = predicate;
predicate = s => p(s) && s.FirstName.Equals(FirstName);
}
}
else
{
predicate = s => true;
}
var query = Students.Where(predicate);
You'll notice it's the exact same if/else structure. You could collapse that down into a complicated conditional expression
Func<Student, bool> predicate;
predicate = s =>
!string.IsNullOrEmpty(StudentNumber)
? s.StudentNumber.Equals(StudentNumber)
: !string.IsNullOrEmpty(LastName)
? !string.IsNullOrEmpty(FirstName)
? s.LastName.Equals(LastName) && s.FirstName.Equals(FirstName)
: s.LastName.Equals(LastName)
: true;
var query = Students.Where(predicate);
But I find that pretty well difficult to follow, certainly as compared to the longer if/else. This predicate is also bigger than the one we build via the if/else, because this one contains all the logic, it's not just the logic we conditionally added.
I want to accept a string array of where conditions from the client like field == value.
It would be really nice to create a specification object that could accept the string in the constructor and output a lambda expression to represent the Where clause. For example, I could do the following:
var myCondition = new Specification<Product>( myStringArrayOfConditions);
var myProducts = DB.Products.Where( myCondition);
How could you turn "name == Jujyfruits" into DB.Products.Where(p => p.name == "JujyFruits")?
You can use
Reflection to get the Property Product.name from the string name and
the LINQ Expression class to manually create a lambda expression.
Note that the following code example will only work for Equals (==) operations. However, it is easy to generalize to other operations as well (split on whitespace, parse the operator and choose the appropriate Expression instead of Expression.Equal).
var condition = "name == Jujyfruits";
// Parse the condition
var c = condition.Split(new string[] { "==" }, StringSplitOptions.None);
var propertyName = c[0].Trim();
var value = c[1].Trim();
// Create the lambda
var arg = Expression.Parameter(typeof(Product), "p");
var property = typeof(Product).GetProperty(propertyName);
var comparison = Expression.Equal(
Expression.MakeMemberAccess(arg, property),
Expression.Constant(value));
var lambda = Expression.Lambda<Func<Product, bool>>(comparison, arg).Compile();
// Test
var prod1 = new Product() { name = "Test" };
var prod2 = new Product() { name = "Jujyfruits" };
Console.WriteLine(lambda(prod1)); // outputs False
Console.WriteLine(lambda(prod2)); // outputs True
About the constructor thing: Since Func<T, TResult> is sealed, you cannot derive from it. However, you could create an implicit conversion operator that translates Specification<T> into Func<T, bool>.
We've recently discovered the Dynamic LINQ library from the VS2008 sample projects. Works perfectly to turn string based "Where" clauses into expressions.
This link will get you there.
You need to turn your search term into a predicate. Try something like the following:
string searchString = "JujyFruits";
Func<Product, bool> search = new Func<Product,bool>(p => p.name == searchString);
return DB.Products.Where(search);
How do I write a dynamic query for Linq, if I have say Customer class which holds the fields:
string name
string address
int phoneno
I have to query based on information given similar to
query = string.Empty;
if(!string.IsNullorEmpty(name))
{
query += "#name = name";
}
if(!string.IsNullorEmpty(address))
{
query += "#address = address";
}
if(!string.IsNullorEmpty(phoneno))
{
query += "#phoneno = phoneno";
}
var result = from condition in customer
where(query)
select condition;
Edit #1:
the items are changeable at run time like
private Customer[] GetCustomers(Dictionary<string,string> attributes)
{
here the attribute may be, name alone, or name and address, or name address and phoneno
foreach(string field in attributes.key)
{
query += field == attributes[key];
}
Customers[] =ExecuteQuery(query);
}
Is this kind of query supported by LINQ?
Edit #2:
Hi Mouk,
As I am new to C#, I am still struggling, this is not working for me.
var query = _ConfigFile.ConnectionMasterSection;
for(int i = 0; i < filter.count; i++)
{
query = result.Where(p => typeof(ConnectionMaster).GetProperty(filter[i].Attribute).Name == filter[i].Value);
}
This yeilds Empty, where as i used this
var query = _ConfigFile.ConnectionMasterSection;
//Hard coded
res.Where(q => q.category == filter[0].Value);
And it worked as I expected.
Hi Bryan Watts,
I tried your code also and I getting this error: "Lambda Parameter not in scope".
for(int i = 0; i < filter.count; i++)
{
Field item = filter[i];
MemberExpression param = Expression.MakeMemberAccess(Expression.Parameter(typeof(Connection), "p"), typeof(Connection).GetProperty(item.Attribute));
MemberExpression constant = Expression.MakeMemberAccess(Expression.Constant(item), typeof(Field).GetProperty("Value"));
}
try
{
var myquery = Queryable.Where(coll, Expression.Lambda<Func<Connection, bool>>(
Expression.Equal(param, constant), Expression.Parameter(typeof(Connection),"p")));
}
What is the mistake here?
Check out this http://www.albahari.com/nutshell/predicatebuilder.aspx, it allows for strongly typed predicate building, it can be really nice. If you want actually dynamic string built predicates than you can use the LINQ Dynamic Query Library provided by ScottGu.
Both will accomplish what you want although I would recommend the first option before the second.
Allowing you to do:
var predicate = PredicateBuilder.True<MyLinqType>();
if(!string.IsNullOrEmpty(name))
predicate = predicate.And(p => p.name == name);
...
var myResults = Context.MyLinTypeQueryTable.Where(predicate);
And more.
Here you go:
var result = from customer in Customers
where string.IsNullOrEmpty(phoneNo) || customer.PhoneNo == phoneNo
where string.IsNullOrEmpty(address) || customer.Address == address
select customer;
If you're concerned that this generate the optimal SQL query underneath, as always you should attach a SQL Query Analyzer and check. But I believe the expression parser in Linq To Sql will collapse down the where clauses as appropriate based on the value of the arguments.
You can use the fluent interface and add a new Where clause fpr each condition. Something like:
var result = from cus in customers select cus;
if(!string.IsNullOrEmpty(name))
result= result.Where(p => p.Name == name);
EDIT upon hte comment:
if you are querying over a collection in memory, you could retrieve the properties using reflection.
private Customer[] GetCustomers(Dictionary<string,string> attributes)
{
var result = from cus in customers select cus;
foreach(string key in attributes.Keys)
result= result.Where(p => GetProperty(p, key )== attributes[key]);
return result.ToList();
}
Supposing GetProperty retrieve the property by reflection.
Using Linq2Sql this method will result in retrieving all record an then iterating over them using reflection.
I've had good experience with Dynamic LINQ.
I used it for a rich HTML table that could be filtered and sorted server side. The server receives a request containing a request parameter where the key is the name of the property (for example 'Lastname') and the value is the value that the property needs to be sorted on (for example 'Smith'). Using that information I built a query string that I passed to the Dynamic LINQ's Where method.
Roughly, you could think of something like the following:
public static IQueryable<T> Filter<T>(this IQueryable<T> query, Dictionary<string, string> dictionary)
{
Type t = typeof(T);
StringBuilder sb = new StringBuilder();
PropertyInfo[] properties = t.GetProperties();
foreach(string key in dictionary.Keys)
{
PropertyInfo property = properties.Where(p => p.Name == key).SingleOrDefault();
if(property != null)
{
if (sb.Length > 0) sb.Append(" && ");
string value = dictionary[key];
sb.Append(string.Format(#"{0}.ToString().Contains(""{1}"")", key, value));
}
}
if (sb.Length > 0)
return query.Where(sb.ToString());
else
return query;
}
The code is out of the top of my head and thus untested.
Of course, this is the most basic version: it does a simple string comparison. If you want to have numerical comparison (meaning you want for example the User where UserID is exactly 100, not where the UserID.ToString().Contains("100")), or query nested Properties (Customer.Company.CompanyAddress for example), or query Collections this gets more complicated. You should also think about security: while Dynamic LINQ is not vulnerable to SQL injection, you shouldn't let it blindly parse all user input.
It sounds like you need to dynamically compose queries.
See my answer to this question.
It explains how queries against an IQueryable<T> are composed by the compiler, and what you can do to add dynamic elements.
Edit
Here is an example of how you would dynamically build a set of Where conditions on top of an IQueryable<Customer>:
// This method ANDs equality expressions for each property, like so:
//
// customers.Where(c => c.Property1 == value1 && c.Property2 == value2 && ...);
private IQueryable<Customer> FilterQuery(IQueryable<Customer> customers, IDictionary<string, string> filter)
{
var parameter = Expression.Parameter(typeof(Customer), "c");
Expression filterExpression = null;
foreach(var filterItem in filter)
{
var property = typeof(Customer).GetProperty(filterItem.Key);
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var equality = Expression.Equal(propertyAccess, Expression.Constant(filterItem.Value));
if(filterExpression == null)
{
filterExpression = equality;
}
else
{
filterExpression = Expression.And(filterExpression, equality);
}
}
if(filterExpression != null)
{
var whereBody = Expression.Lambda<Func<Customer, bool>>(filterExpression, parameter);
customers = customers.Where(whereBody);
}
return customers;
}