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.
Related
I love to build LINQ to entity queries dynamically. For simple a simple filter I do something like this:
var query = model.Pets;
if(searchForName)
query = query.Where(p => p.Name == "Bello");
if(searchForType)
query = query.Where(p => p.Type == 1);
var result = query.ToList();
Now, I want exactly this type of dynamic query building for a more complex query.
This is the new (sample) schema:
Is there a way to generate a resulting query to select all Pets but only include Owner with some properties?
The results should be:
model.Pets.Include(p.Owner);
or
model.Pets.Include(p.Owner.Where(o => o.Name == "Hans"));
or
model.Pets.Include(p.Owner.Where(o => o.Gender == 0));
or
model.Pets.Include(p.Owner.Where(o => o.Name == "Hans").Where(o => o.Gender == 0));
I didn't find a solution to inject a Where clause in the middle of a query.
The above sample is simplified, I need this for more complex queries with joins and subselects.
Maybe you can use the query syntax (which is, in my opinion, much easier to use) :
List<Pets> petsList = (from pet in model.Pets
join owner in model.Owner on pet.Owner_name equals owner.Name
where owner.Name.Equals("Hans") || owner.Name.Equals("James")
where pet.Type == 1
select pet).ToList();
Hope this helps.
PS : for primary key, prefer integer instead of strings.
I would recomend you to user PredicateBuilder
please see the site http://www.albahari.com/nutshell/predicatebuilder.aspx
it'll be more easier to build your dynamic filters
you can even use methods
public static Expression<Func<Product, bool>> ContainsInDescription (
params string[] keywords)
{
var predicate = PredicateBuilder.False<Product>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or (p => p.Description.Contains (temp));
}
return predicate;
var classics = Product.ContainsInDescription ("Nokia", "Ericsson")
.And (Product.IsSelling());
I have a question regarding a Linq to SQL query.
I have following situation:
I have a search with lots of options, like location, availability, name, language etc ...
For this options i have to execute a query to retrieve the results according to options selected, how can i best do it, i cannot write a linq query like for each possibility and combination of options, but i cannot write one for all of them as it will not work, for example:
from p in context.people where p.location==model.location && p.availability==model.availability .... select p
In this case imagine availability is not selected and should not be searched for, but in this case it will be passed as false, or if location is not set and is null so it will only search for empty locations, although i just need all.
So my question is how do people handle this kind of behaviour with queries?
As you long as you do not execute the linq query immediately you can just add where clauses to it. You can do this for example:
var query = from p in context.people;
if(searchOnLocation)
{
query = query.where(p => p.location == model.location);
}
if(otherSearch)
{
query = query.where(p => p.someOtherProperty == someotherValue);
}
var result = query.ToList();
As long you don't call ToList() on your IQueryable, the linq will not be translated into SQL. It's only in the last call, that the linq will be translated and executed against the database
IQueryable<Person> query = context.people;
if(model.location != null)
query = query.Where(x => x.location == model.location);
if(model.availability != null)
query = query.Where(x => x.availability == model.availability);
// etc
Basically, you can compose more and more restrictions as you go.
If you want to implement query without if condition than you can use following syntax:
var query = context.people.
where(p => p.location == (model.location ?? p.location)
&& p.availability == (model.availability ?? p.availability))
.ToList();
I am trying to build a method in my asp.net WebAPI to grab data based on the arguments passed on the method. The method is used to perform a search on restaurant data. I have a variable called 'type' that determines the type of data search performed. The second variable 'keyword' is the keyword searched by the user. The WHERE condition in my LINQ query depends on the type and needs to be dynamic, so I have used a separate variable outside the LINQ query to define the condition. I have tried assigning this variable to my WHERE statement on the LINQ query but it doesn't seem to work. Can someone help with it please? I have been stuck on this for a few days now
public IQueryable<RestaurantView> GetRestaurantsForSearch(string keyword, int type, string location)
{
//
var condition = "";
if(type == 1)
{
condition = "x.RestaurantName.Contains(keyword)";
} else if(type == 2){
condition = "x.Cuisine.Equals(keyword)";
}
else {
condition = "x.Rating.Equals(keyword)";
}
var query = from x in db.Restaurants
join y in db.Cuisine on x.RestaurantCuisine equals y.CuisineID
where condition
select new RestaurantView
{
RestaurantID = x.RestaurantID,
RestaurantName = x.RestaurantName,
RestaurantCuisine = y.CuisineName,
RestaurantDecription = x.RestaurantDecription
};
return query;
}
Try this:
Predicate<Restaurant> pred;
if (type == 1) pred = x => x.RestaurantName.Contains(keyword);
else if (type == 2) pred = x => x.Cuisine.Equals(keyword);
else pred = x => x.Rating.Equals(keyword);
var query = from x in db.Restaurants
join y in db.Cuisine on x.RestaurantCuisine equals y.CuisineID
where pred(x)
select new RestaurantView
{
RestaurantID = x.RestaurantID,
RestaurantName = x.RestaurantName,
RestaurantCuisine = y.CuisineName,
RestaurantDecription = x.RestaurantDecription
};
return query;
You need to look a dynamic linq library i think then you can execute string statements inside your linq
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
or you can execute direct query
http://msdn.microsoft.com/en-us/library/system.data.linq.datacontext.executequery.aspx
If you are ok with dropping your comprehensive LINQ query in favour of the extension method syntax, it's pretty simple (I'm on a netbook without VS, so I apologize that this is untested but should give you the idea):
var query = db.Restaurants
.Include("Cuisine")
if(type == 1)
{
query= query.Where(x => x.RestaurantName.Contains(keyword));
}
else if(type == 2)
{
query = query.Where(x => x.Cuisine == keyword);
}
else {
query = query.Where(x => x.Rating == keyword);
}
This builds out your expression tree differently based on your logic checks, which will result in a different SQL query being generated based on the value of type.
I notice that in your join, Cuisine appears to be an Entity, but in your logic checks, you attempt to filter by comparing Cuisine to a string so I think there is some disconnect.
var query = from x in db.Restaurants
join y in db.Cuisine on x.RestaurantCuisine equals y.CuisineID
where condition
select new RestaurantView
{
RestaurantID = x.RestaurantID,
RestaurantName = x.RestaurantName,
RestaurantCuisine = y.CuisineName,
RestaurantDecription = x.RestaurantDecription
};
return query;
}
how to get the return query value in client side to assign for grid view binding
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();
Ingredient class:
class Ingredient
{
public String Name { get; set; }
public Double Amount { get; set; }
}
List of Ingredients:
var ingredientsList = new List<Ingredient>();
Database layout of my "Ingredients" table:
[Ingredients] (
[IngredientsID] [int] IDENTITY(1,1) NOT NULL,
[RecipeID] [int] NOT NULL,
[IngredientsName] [nvarchar](512) NOT NULL,
[IngredientsAmount] [float] NOT NULL
)
Am I able to query my ingredientsList against my "Ingredients" table, doing a where-clause which goes something like this (pseudo code alert!):
SELECT * FROM Ingredients WHERE
IngredientsName = ["Name" property on entities in my ingredientsList] AND
IngredientsAmount <= ["Amount" property on entities in my ingredientsList]
I of course want this to be done with LINQ, and not using dynamically generated SQL queries.
LINQ is composable, but to do this without using UNION you'd have to roll your own Expression. Basically, we (presumably) want to create TSQL of the form:
SELECT *
FROM [table]
WHERE (Name = #name1 AND Amount <= #amount1)
OR (Name = #name2 AND Amount <= #amount2)
OR (Name = #name3 AND Amount <= #amount3)
...
where the name/amount pairs are determined at runtime. There is easy way of phrasing that in LINQ; if it was "AND" each time, we could use .Where(...) repeatedly. Union is a candidate, but I've seen repeated people have problems with that. What we want to do is emulate us writing a LINQ query like:
var qry = from i in db.Ingredients
where ( (i.Name == name1 && i.Amount <= amount1)
|| (i.Name == name2 && i.Amount <= amount2)
... )
select i;
This is done by crafting an Expression, using Expression.OrElse to combine each - so we will need to iterate over our name/amount pairs, making a richer Expression.
Writing Expression code by hand is a bit of a black art, but I have a very similar example up my sleeve (from a presentation I give); it uses some custom extension methods; usage via:
IQueryable query = db.Ingredients.WhereTrueForAny(
localIngredient => dbIngredient =>
dbIngredient.Name == localIngredient.Name
&& dbIngredient.Amount <= localIngredient.Amount
, args);
where args is your array of test ingredients. What this does is: for each localIngredient in args (our local array of test ingredients), it asks us to provide an Expression (for that localIngredient) that is the test to apply at the database. It then combines these (in turn) with Expression.OrElse:
public static IQueryable<TSource> WhereTrueForAny<TSource, TValue>(
this IQueryable<TSource> source,
Func<TValue, Expression<Func<TSource, bool>>> selector,
params TValue[] values)
{
return source.Where(BuildTrueForAny(selector, values));
}
public static Expression<Func<TSource, bool>> BuildTrueForAny<TSource, TValue>(
Func<TValue, Expression<Func<TSource, bool>>> selector,
params TValue[] values)
{
if (selector == null) throw new ArgumentNullException("selector");
if (values == null) throw new ArgumentNullException("values");
// if there are no filters, return nothing
if (values.Length == 0) return x => false;
// if there is 1 filter, use it directly
if (values.Length == 1) return selector(values[0]);
var param = Expression.Parameter(typeof(TSource), "x");
// start with the first filter
Expression body = Expression.Invoke(selector(values[0]), param);
for (int i = 1; i < values.Length; i++)
{ // for 2nd, 3rd, etc - use OrElse for that filter
body = Expression.OrElse(body,
Expression.Invoke(selector(values[i]), param));
}
return Expression.Lambda<Func<TSource, bool>>(body, param);
}
The only extent to which you can use a local collection in a LINQ 2 SQL query is with the Contains() function, which is basically a translation to the SQL in clause. For example...
var ingredientsList = new List<Ingredient>();
... add your ingredients
var myQuery = (from ingredient in context.Ingredients where ingredientsList.Select(i => i.Name).Contains(ingredient.Name) select ingredient);
This would generate SQL equivalent to "...where ingredients.Name in (...)"
Unfortunately I don't think that's going to work for you, as you'd have to join each column atomically.
And just as an aside, using LINQ 2 SQL is a dynamically generated SQL query.
You could, of course, do the joining on the client side, but that would require bringing back the entire Ingredients table, which could be performance-prohibitive, and is definitely bad practice.
I think you'll either have to use multiple queries, or copy your ingredients list into a temporary table and do the database query in that way.
I mean, you could have a SQL statement of:
SELECT * FROM Ingredients WHERE
(IngredientsName = 'Flour' AND IngredientsAmount < 10) OR
(IngredientsName = 'Water' AND IngredientsAmount <= 5) OR
(IngredientsName = 'Eggs' AND IngredientsAmount <= 20)
but it get ugly pretty quickly.
Personally I suspect that the temporary table solution is going to be the neatest - but I don't know whether LINQ to SQL has much support for them.
List<string> ingredientNames = ingredientsList
.Select( i => i.Name).ToList();
Dictionary<string, Double> ingredientValues = ingredientsList
.ToDictionary(i => i.Name, i => i.Amount);
//database hit
List<Ingredient> queryResults = db.Ingredients
.Where(i => ingredientNames.Contains(i.Name))
.ToList();
//continue filtering locally - TODO: handle case-sensitivity
List<Ingredient> filteredResults = queryResults
.Where(i => i.Amount <= ingredientValues[i.Name])
.ToList();
I was messing around with this solution in LINQPad, if you have it, you can see the dump outputs. Not sure if it is what you need, but from what I understand it is. I used it against my Users table, but you could replaced that for Ingredients and "UserList" for "IngredientList" and "Username" for "Ingredient Name". You can add further "OR" filtering expressions inside the if statement. It is important you set an ID though.
So final note the "Dump()" method is specific to LINQPad and is not required.
var userList = new List<User>();
userList.Add(new User() { ID = 1, Username = "goneale" });
userList.Add(new User() { ID = 2, Username = "Test" });
List<int> IDs = new List<int>();
// vv ingredients from db context
IQueryable<User> users = Users;
foreach(var user in userList)
{
if (users.Any(x => x.Username == user.Username))
IDs.Add(user.ID);
}
IDs.Dump();
userList.Dump();
users.Dump();
users = users.Where(x => IDs.Contains(x.ID));
users.Dump();
I am using Union to concatinate results of each subquery:
public static IQueryable<TSource> WhereTrueForAny<TSource, TValue>(this IQueryable<TSource> source, Func<TValue, Expression<Func<TSource, bool>>> selector, params TValue[] values)
{
// code is based on Marc Gravells answer
if (selector == null) throw new ArgumentNullException("selector");
if (values == null) throw new ArgumentNullException("values");
// if there are no filters, return nothing
if (values.Length == 0) return source.Where(x => false);
// if there is 1 filter, use it directly
if (values.Length == 1) return source.Where(selector(values[0]));
var lockingUpArray = values;
var p = lockingUpArray.First();
IQueryable<TSource> query = source.Where(selector(p));
foreach (var param in lockingUpArray.Skip(1))
{
query = query.Union(source.Where(selector(param)));
}
return query;
}