Reduce linq query for filtering - c#

I have a view with 3 textboxes which bind to properties in the ViewModel SupplierName, Contact, Address and one button which bind to SearchCommand property in my ViewModel.
My requirement is to filter Supplier records based on the above properties. I used EntityFramework.
The user can enter any of the above textboxes which lead me to write 9 different queries.
For instance if the user inputs data only on the SupplierName textbox then I need to run one query with SupplierName as parameter. If the user enters SupplierName and Contact textboxes then I need to run another query. And so on.
Here is my code:
public IEnumerable<Model.Supplier> GetAllSuppliersBySearch(string nameMatch, string contactMatch, string phoneMatch)
{
if(nameMatch!=null)
{
var q = from f in Context.Suppliers
where f.SupplierName==nameMatch
select f;
}
else if(contactMatch!=null)
{
var q = from f in Context.Suppliers
where f.ContactName==contactMatch
select f;
}
else if(phoneMatch!=null)
{
var q = from f in Context.Suppliers
where f.ContactName==contactMatch
select f;
}
return q.AsEnumerable();
}
Instead of writing multiple queries, how to accomplish this with one query or in any optimized way?

Compose query with lambda syntax:
IQueryable<Supplier> query = Context.Suppliers;
if (!String.IsNullOrEmpty(nameMatch))
query = query.Where(s => s.SupplierName == nameMatch);
if (!String.IsNullOrEmpty(contactMatch))
query = query.Where(s => s.ContactName == contactMatch);
// etc
return query.AsEnumerable();
Another option is adding parameter-checking conditions to query
var query =
from s in Context.Suppliers
where (String.IsNullOrEmpty(nameMatch) || s.SupplierName == nameMatch) &&
(String.IsNullOrEmpty(contactMatch) || s.ContactName == contactMatch)
// etc
select s;

Related

Update IQueryable result before using as join in next query

I need to use Linq to Entity Framework to query a LOCATION table to get the record of the location code with the MAX effective date, then use that result as a join in the next query.
I BELIEVE I need to do convert before the IQueryable is used, because I have that last clause in the second query where I want to exclude records where the FLOOR code is in the excludedSchools list. That excludedSchools list will have the newLocationCode in it.
So, I need to update the values in the IQueryable result before I use it. Can I do this? Here is my code:
using (var db = new TheContext())
{
IQueryable<LocationTable> locatinWithMaxEffDate =
(from lc in db.LocationTable
where lc.EFF_STATUS == "A" && lc.EFFDT <= DateTime.Now
group lc by lc.LOCATION into g
select g.OrderByDescending(x => x.EFFDT).FirstOrDefault()
);
foreach (var location in locatinWithMaxEffDate.ToList())
{
string newLocationCode;
if(codeMappingDictionary.TryGetValue(location.FLOOR, out newLocationCode))
{
// how do I update locatinWithMaxEffDate FLOOR value
// with newLocationCode so it works in the query below?
location.FLOOR = newLocationCode;
}
}
var query =
(from fim in db.PS_PPS_FIM_EE_DATA
join mloc in locatinWithMaxEffDate on fim.LOCATION equals mloc.LOCATION
where
fim.EMPL_STATUS == PsPpsFimEeData.EmployeeStatusValues.Active
&& fim.AUTO_UPDATE == PsPpsFimEeData.AutoUpdateValues.Enabled
&& includeJobCodes.Contains(fim.JOBCODE)
&& !excludedSchools.Contains(mloc.FLOOR)
select new PpsAdministratorResult
{
SchoolId = mloc.FLOOR,
Login = fim.OPRID,
EmployeeId = fim.EMPLID,
}
With the code above, the locatinWithMaxEffDate does not have the updated FLOOR values. I can see why this is, but can't seem to fix it.
So far, I have tried introducing another list to ADD() the new location record to, then casting that as an IQueryable, but I get an error about primitive vs concrete types.
I decided to make things easier on myself. Since both sets of data are very small (fewer than 1000 records each) I call take the entire set of data as an annonymous type:
using (var db = new TheContext())
{
IQueryable<LocationTable> locatinWithMaxEffDate =
(from lc in db.LocationTable
where lc.EFF_STATUS == "A" && lc.EFFDT <= DateTime.Now
group lc by lc.LOCATION into g
select g.OrderByDescending(x => x.EFFDT).FirstOrDefault()
);
var query =
(from fim in db.PS_PPS_FIM_EE_DATA
join mloc in locatinWithMaxEffDate on fim.LOCATION equals mloc.LOCATION
where
fim.EMPL_STATUS == PsPpsFimEeData.EmployeeStatusValues.Active
&& fim.AUTO_UPDATE == PsPpsFimEeData.AutoUpdateValues.Enabled
&& includeJobCodes.Contains(fim.JOBCODE)
select new PpsAdministratorResult
{
SchoolId = mloc.FLOOR,
Login = fim.OPRID,
EmployeeId = fim.EMPLID,
}
}
Then, just work with the two objects:
List<PpsAdministratorResult> administratorList = new List<PpsAdministratorResult>();
foreach (var location in query.ToList())
{
string newLocationCode;
if(schoolCodeMappings.TryGetValue(location.SchoolId, out newLocationCode)) // && newLocationCode.Contains(location.LOCATION))
{
location.SchoolId = newLocationCode;
}
if( !excludedSchools.Contains(location.SchoolId) )
{
administratorList.Add(location);
}
}
Now, I have the list I want.

How do I order by a function in LINQ query?

I am developing my first MVC application with Entity Framework. I have a table USERS and a table RESTRICTIONS
In my controller I wrote a function that returns the number of common restrictions between two users:
public int common_restrictions(int id1, int id2)
{
MyModel bd = new MyModel();
int count= 0;
var restrictions = from c in bd.RESTRICTIONS where c.ID_USER == id1
select c;
var restrictions2 = from c in bd.RESTRICTIONS where c.ID_USER == id2
select c;
foreach (var prop in restrictions)
{
var nr = restrictions2.Count(p => p.ID_PROP == prop.ID_PROP);
if (nr != 0)
{
count++;
}
}
return count;
}
The function works as it supposed to.
Now in another function in the same controller I want to sort the list of users in descending order of their common restrictions with a specific user (let's say user with the id 12). I got the list of users in a query but I don't know how to sort them after that
var query = from u in bd.USERS where u.ID != 12
select u;
// sort the list??
I've tried
var query = from u in bd.USERS orderby(common_restrictions(u.ID,
12)) select u;
but I get the following error message:
"LINQ to Entities does not recognize the method 'Int32 common_restrictions (Int32, Int32)' method, and this method cannot be translated into a store expression."
You need to do this in two steps if you don't want to include another property to your User object. And I think this following way is easier.
var query = (from u in bd.USERS where u.ID != 12
select u).ToList();
var unSorteUsers = (from u in query
select new
{
User = u,
CR = common_restrictions(u.ID,12)
});
var sortedUsers = (from u in unSorteUsers
orderby u.CR
select new User
{
ID = u.User.ID,
//All other properties.
});

Non-mandatory fields in the search

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

Make a LINQ query dynamic to bring back all rows or only rows with a link to a lookup table?

I have a query that returns a list of currencies and joins to a lookup table. The result is then put into a class object (which works fine):
var queryforobject = from x in db.CurrencyExchangeRates.AsNoTracking()
join c in db.CurrencyTypes.AsNoTracking() on x.CurrencyTypeID equals c.ID
orderby x.ID
select new CurrencyExchangeRateObject
{
ID = x.ID,
CurrencyID = c.ID,
Currency = c.Description,
ExchangeRate = x.ExchangeRate,
LastEditedDate = x.LastEditedDate,
LastEditedBy = x.LastEditedBy,
Active = x.Active
};
I want to make this more dynamic, so if no CurrencyTypeID is supplied then it will return the full list (as it does already) - otherwise if a CurrencyTypeID is supplied it will only show where X.CurrencyTypeID = ID.
Something along the lines of an inline if?
There are a few options for filtering the query based on CurrencyTypeID if a search value (named currencyTypeID in this answer) is supplied, but return all data if no currencyTypeID is supplied.
First option: You could add a where clause to your existing query expression. The WHERE clause below will return every record in the data set if null is passed in for the currencyTypeID variable, otherwise it will filter the results.
from x in db.CurrencyExchangeRates.AsNoTracking()
join c in db.CurrencyTypes.AsNoTracking() on x.CurrencyTypeID equals c.ID
where (currencyTypeID == null || x.CurrencyTypeID == currencyTypeID)
orderby x.ID
select new CurrencyExchangeRateObject {
ID = x.ID,
CurrencyID = c.ID,
Currency = c.Description,
ExchangeRate = x.ExchangeRate,
LastEditedDate = x.LastEditedDate,
LastEditedBy = x.LastEditedBy,
Active = x.Active
};
Alternatively: Since queryforobject is of type IQueryable<T>, you can use LINQ's fluent API to append a WHERE clause to the query inside an if statement. You need to be more careful about timing on this one though as it needs to be done before you force evaluation of the IQueryable with a foreach loop, .ToList(), .Select() or other LINQ methods that force evaluation.
if(currencyTypeID != null)
queryforobject = queryforobject.Where(cerObj => cerObj.CurrencyID == currencyTypeID);

Filtering data with multiple filter values

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.

Categories