I have a form with multiple search criteria that a user can use to search for employee data, e.g. FirstName, LastName, HireDate, Department, etc.
I am using LINQ and am wondering what method could I use to query a collection of Employes given any of of the search criteria, i.e. a user does not have to enter all, but they do have to enter at least one of the search parameters.
So far, while testing my LINQ statement with two search parameters in place, it seems that I have to see if the search parameter is entered or not.
If this is the case, then this can get quite unwieldy for many search parameters.
// only FirstName is entered
if (!string.IsNullOrEmpty(FirstName) && string.IsNullOrEmpty(LastName))
{
var employees = DB.Employees
.Where(emp => emp.FirstName.Contains(fName));
}
// only LastName is entered
else if (string.IsNullOrEmpty(FirstName) && !string.IsNullOrEmpty(LastName))
{
var employees = DB.Employees
.Where(emp => emp.LastName.Contains(lName));
}
// both parameters are entered
else if (!string.IsNullOrEmpty(FirstName) && !string.IsNullOrEmpty(LastName))
{
var employees = DB.Employees
.Where(emp => emp.FirstName.Contains(fName))
.Where(emp => emp.LastName.Contains(lName));
}
FYI, I initially thought that I could just append Where() statements to my LINQ statement with the pertinent search parameters but I noticed that not all records were being returned that should and thus the above logic of if-then statements.
What about something like this:
IQueryable<Employee> employees = DB.Employees;
if (!string.IsNullOrEmpty(FirstName))
{
employees = employees
.Where(emp => emp.FirstName.Contains(fName));
}
if (!string.IsNullOrEmpty(LastName))
{
employees = employees
.Where(emp => emp.Last.Contains(lName));
}
You can write it like this:
var employees = DB.Employees.AsQueryable();
if (!string.IsNullOrEmpty(fName)
employees = employees.Where(emp => emp.FirstName.Contains(fName));
if (!string.IsNullOrEmpty(lName)
employees = employees.Where(emp => emp.LastName.Contains(lName));
I encountered a similar challenge where a user could select 0, 1 or many values for about 10 searchable fields and needed to construct that query at runtime.
I ended up using LINQKit:
http://www.albahari.com/nutshell/linqkit.aspx
In particular I used it's predicate builder, which is described here:
http://www.albahari.com/nutshell/predicatebuilder.aspx
In your example above, you've encompassed the query multiple within if statements.
The alternative is to build the query as you go.
If you were to declare var employees = DB.Employees outside of those if statements (Assuming that it's always relevant), then you could just tack on your where statements within your if statements if they're applicable.
LINQ gives you deferred execution, so you don't have to have the entire expression in a single block (Even though it feels most natural to do so and in many cases you will).
Things get a bit more complicated if you want to mix in OR's with ANDs, but that's where the previously mentioned predicate builder comes in.
Unfortunately I don't have any examples to share, but those links should get you off to a good start.
var resultData = (from data in db.Abc
where !string.IsNullOrEmpty(firstName) ? data.FirstName == firstName : true
&& data.UserType == userTypeValue
&& !string.IsNullOrEmpty(lastName) ? data.LastName == lastName : true
&& !string.IsNullOrEmpty(gender) ? data.Gender == gender : true
&& !string.IsNullOrEmpty(phone) ? data.CellPhone == phone : true
&& !string.IsNullOrEmpty(fax) ? data.Fax == fax : true
&& !string.IsNullOrEmpty(emailAddress) ? data.Email == emailAddress : true
&& !string.IsNullOrEmpty(address1) ? data.Address == address1 : true
select new
{
UserName = data.UserName,
FirstName = data.FirstName,
Address = data.Address,
CellPhone = data.CellPhone,
Fax = data.Fax,
Email = data.Email
}).ToList();
Related
This question already has answers here:
How to chain OR clauses, with LINQ?
(2 answers)
Closed 2 years ago.
I'm writing a query where, based on what input the user has provided, I need to add "disjunctive" conditions (that is to say "or" conditions). To do this with "conjunctive" conditions ("and" conditions"), this is fairly straight forward...
var employees = repository.GetAll<Employee>(); // returns IQueryable
if (!string.IsNullOrEmpty(firstName))
employees = employees.Where(e => e.FirstName.Contains(firstName));
if (!string.IsNullOrEmpty(lastName))
employees = employees.Where(e => e.LastName.Contains(lastName));
if (!string.IsNullOrEmpty(email))
employees = employees.Where(e => e.Email.Contains(email));
The resulting query will have 0 to 3 AND conditions depending on whether firstName, lastName, or email are populated. But is there a way to do this using OR conditions? For example, is there something like the following...
// Notice these use of OrWhere which does not exist...
var employees = repository.GetAll<Employee>(); // returns IQueryable
if (!string.IsNullOrEmpty(firstName))
employees = employees.OrWhere(e => e.FirstName.Contains(firstName));
if (!string.IsNullOrEmpty(lastName))
employees = employees.Orwhere(e => e.LastName.Contains(lastName));
if (!string.IsNullOrEmpty(email))
employees = employees.OrWhere(e => e.Email.Contains(email));
I know that I can make a very large where condition...
var employees = repository.GetAll<Employee>().Where(e =>
(!string.IsNullOrEmpty(firstName) && e.FirstName.Contains(firstName)) ||
(!string.IsNullOrEmpty(lastName) && e.LastName.Contains(lastName)) ||
(!string.IsNullOrEmpty(email) && e.Email.Contains(email))
);
...but the query produced is not efficient and the code does not seem as elegant to me. I'm hoping there's a better way to do this that looks more like the 2nd example.
Here is one way using OR conditions between the null check and the property filter, if that's what you're after...
var employees = repository.GetAll<Employee>(); // returns IQueryable
employees = employees.Where(e =>
(firstName == null || e.FirstName.Contains(firstName)) &&
(lastName == null || e.LastName.Contains(lastName)) &&
(email == null || e.Email.Contains(email)));
I have two tables (tbPerson and tbDataLog) where I need to return Id from one table (tbPerson) after checking certain conditions on both. After this, this result should be passed to another query. My first query returns the Id (primary key of a table) successfully and I need to pass these ids to another query so that it return me data based upon these Id. I also has an IQueryable type base object to check certain conditions to fetch data.
IQueryable<tbPerson> dataset
and I cannot changes this from Iqueryable to other as it will break other part of the code)
My first linq statement:
public static IQueryable<LogResults> GetResultsForYes()
{
Databasename ents = new Databasename();
var ids = (from f in ents.tbPerson
join g in ents.tbDataLog
on f.InfoID equals g.RefId
where g.Tag == "subscribed" && g.OldValue == "No" && g.Action == "Modified"
select new LogResults { _LogID = f.Id }).OrderBy(x => x._LogID);
return ids;
}
public class LogResults
{
public int _LogID { get; set; }
}
I access my result something like this where I can see in debugger all the Ids.
IQueryable<LogResults> log = GetResultsForYes();
Problem comes, when I tried to get records from tbPerson based upon these returned Id.
dataset=log.where(x=>x._LogID != 0);
I get this error:
Cannot implicitly convert type 'System.Linq.IQueryable' to 'System.Linq.IQueryable'. An explicit conversion exists(are you missing a cast)?
Any suggestions or some other good approach is welcome.
I love this thing about stackoverflow. when we write questions we force our brain to think more deeply and after 30 mins of posting this question, I solved it in a simple way. Sometimes we overcomplicated things!
var ids = (from f in ents.tbPerson
join g in ents.tbDataLog
on f.InfoID equals g.RefId
where g.Tag == "subscribed" && g.OldValue == "No" && g.Action == "Modified"
select new { f.Id }).ToArray();
var allId = ids.Select(x => x.Id).ToArray();
dataset = dataset.Where(x => allId.Contains(x.Id));
#ankit_sharma : I have not tested yours but will give a try and come back to you. Thanks for giving time and effort.
IQueryable<tbPerson> dataset=log.where(x=>x._LogID != 0);
The result of log.where(x=>x._LogID != 0) is an IQueryable<LogResults>, and you are trying to assign this result to dataset of type IQueryable<tbPerson>, two diferent types.
EDIT:
I see you make a join to get the tbPerson ids, and then you do a second query to get the persons. You could get the persons in the first join.
I just modify your code:
IQueryable<tbPerson> persons = from person in ents.tbPerson
join g in ents.tbDataLog
on person.InfoID equals g.RefId
where g.Tag == "subscribed" && g.OldValue == "No" && g.Action == "Modified"
select person;
I have this piece of the code intended to search a database. A user should have 3 options here: to type surname only, the first name and the user can search using both of them - surname and the first name.
This code retrieves the records from my db if I provide both strings - surname and the first name. But if I type only one of them, my resulting list is always empty.
var query = from x in db.people
where (txtSurname == null || x.Surname== txtSurname.Text)
&& (txtFirstName == null || x.FirstName == txtFirstName.Text)
select x;
var data = query.ToList();
peopleBindingSource.DataSource = data;
Remember that an Entity Framework query doesn't get sent to the database until you materialise the data wth ToList or iterating over it for example. This means you can build up the query in code like this:
var query = db.people.AsQueryable();
if(!string.IsNullOrEmpty(txtSurname.Text))
{
query = query.Where(p => p.Surname == txtSurname.Text);
}
if(!string.IsNullOrEmpty(txtFirstName.Text))
{
query = query.Where(p => p.FirstName == txtFirstName.Text);
}
peopleBindingSource.DataSource = query.ToList();
Hello fellow stackoverflowers,
I'm currently working on a project which gives me a bit of trouble concerning filtering data from a database by using multiple filter values. The filter happens after selecting the filters and by clicking a button.
I have 5 filters: Region, Company, Price, and 2 boolean values
Note that Region and Company are special dropdownlist with checkboxes which means the user can select one or more regions and company names.
I already made a few tests and came up with a incomplete code which works a bit but not to my liking.
Problems arise when one of my filters is NULL or empty. I don't really know how to process this. The only way i thought of was using a bunch of IF ELSE statements, but i'm starting to think that this will never end since there are so much possibilities...
I'm sure there is a far more easier way of doing this without using a bunch of IF ELSE statements, but i don't really know how to do it. If anyone could steer me in the right direction that would be appreciated. Thanks
Here is what i have right now (I haven't added the Price to the query for now):
protected void filterRepeater(List<int> regionIDs, string[] companyArray,
string blocFiltValue, bool bMutFunds, bool bFinancing)
{
DatabaseEntities db = new DatabaseEntities();
PagedDataSource pagedDsource = new PagedDataSource();
IQueryable<Blocs> query = (from q in db.Blocs
where q.isActive == true
orderby q.date descending
select q);
IQueryable<Blocs> queryResult = null;
//if some filters are NULL or Empty, it create a null queryResult
queryResult = query.Where(p => companyArray.Contains(p.company) &&
regionIDs.Contains((int)p.fkRegionID) &&
(bool)p.mutual_funds == bMutFunds &&
(bool)p.financing == bFinancing);
if (queryResult.Count() > 0)
{
//Bind new data to repeater
pagedDsource.DataSource = queryResult.ToArray();
blocRepeater.DataSource = pagedDsource;
blocRepeater.DataBind();
}
}
Only add the relevant filters to query:
IQueryable<Blocs> query =
from q in db.Blocs
where q.isActive == true
orderby q.date descending
select q;
if (companyArray != null)
{
query = query.Where(p => companyArray.Contains(p.company));
}
if (regionIDs != null)
{
query = query.Where(p => regionIDs.Contains((int)p.fkRegionID));
}
// ...
// etc
// ...
if (query.Any()) // Any() is more efficient than Count()
{
//Bind new data to repeater
pagedDsource.DataSource = query.ToArray();
blocRepeater.DataSource = pagedDsource;
blocRepeater.DataBind();
}
If you want to filter only by the filter values that are not null or empty then you can construct the query by appending the where clauses one by one:
if(companyArray != null && companyArray.Length > 0) {
query = query.Where(p => companyArray.Contains(p.company));
}
if(regionIDs!= null && regionIDs.Length > 0) {
query = query.Where(p => regionIDs.Contains((int)p.fkRegionID));
}
if (!String.IsNullOrEmpty(blocFiltValue)) {
query = query.Where(p => p.Block == blocFiltValue);
}
Also you can use nullable values for value types, if you need to filter them optionally
bool? bMutFunds = ...; // Possible values: null, false, true.
...
if(bMutFunds.HasValue) {
query = query.Where(p => (bool)p.mutual_funds == bMutFunds.Value);
}
Maybe you can create a string for the SQL sentence, and dynamically add parts to this sentence like if something was selected or checked you add something to this string when thh selection was completed by the user you can execute this SQL sentence.
consider the following code, which represents an attempt to implement partial matching. the intended result is a row for any 1 or more fields that match between the query entity and the data store.
so if you supply person.email we want a match against that, if you supply person.email and person.FirstName we should filter the results further, and so on.
var results = from p in db.Persons
where p.CrookBookUserName.Trim().Contains(person.CrookBookUserName.Trim()) ||
p.email.Trim().Contains(person.email.Trim()) ||
p.FirstName.Trim().Contains(person.FirstName.Trim()) ||
p.LastName.Trim().Contains(person.LastName.Trim()) ||
p.phone.Trim().Contains(person.phone.Trim())
select p;
return results;
unfortunately, this code always returns all rows in the db. why, and what should be the fix?
thanks in advance.
Does person have all fields populated? A p.email.Trim().Contains("") from an empty email address will return true for everything.
An extra (!String.IsNullOrEmpty(..)) && before each parameter, while verbose, will fix it if this is the problem.
Edit re comments:
Yeah, a helper method or something could help here... something like
public static bool ContainsIfNotEmptyTrimmed(this string source, string param)
{
return !String.IsNullOrEmpty(param.Trim()) && source.Trim().Contains(param.Trim());
}
where p.CrookBookUserName.ContainsIfNotEmptyTrimmed(person.CrookBookUserName) || ...
You need to check if the user is providing empty strings.
var query = db.Persons;
if (!string.IsNullOrEmpty(person.CrookBookUserName.Trim()) {
query = query.Where(p => p.CrookBookUserName.Trim().Contains(person.CrookBookUserName.Trim()));
}
if (!string.IsNullOrEmpty(person.email.Trim()) {
query = query.Where(p => p.email.Trim().Contains(person.email.Trim()));
}
// etc...
return query;