Filtering data with multiple filter values - c#

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.

Related

How do optimize nested loop and filter from another list using linq c#

I am trying to filter a flattened nested loop and filter from another list. So below is what I have been able to do. I tried this approach but when I run the query it does not save anything in the database. I need help to check and ignore existing records (same MemberId and Description) in the Tasklist table.
var addedtop = from a in db.TaskLists select new {a.MemberId,a.Description};
var membtasks = from m in members
from n in tasks
select new { m, n };
TaskList taskList = new TaskList();
foreach (var item in membtasks)
{
var exist = db.TaskLists.Where(a => a.Description == item.n && a.MemberId == item.m);
if(exist == null)
{
taskList .Description = item.n;
taskList .MemberId = item.m;
db.taskList .Add(taskList );
db.SaveChanges();
}
}
return Redirect(url);
The problem is exists will never be null. The line var exist = db.TaskLists.Where(a => a.Description == item.n && a.MemberId == item.m); returns an IQueryable that describes your query but hasn't actually executed against the database yet.
Try changing the line to:
var exist = db.TaskLists.Where(a => a.Description == item.n && a.MemberId == item.m).SingleOrDefault();
This executes the query and checks if there is a single item that satisfies your query. If there is no result at all the query returns null which will execute the code inside your if statement.
Your statement has not been executed query, you can change Where to Any like this:
var exist = db.TaskLists.Any(a => a.Description == item.n && a.MemberId == item.m);
Any function that returns data type is bool

How to create dynamic Multiple And linq conditions in foreach loop

if (rowCount == 1)
{
query =
(from x in partJoinTableRepository.GetPartJoinQuery()
join y in partRepository.GetPartsQuery() on x.PartId equals y.Id
join z in partProductTypeReposiotry.GetPartProductTypesQuery() on x.PartId equals z.PartId
where y.IsSkipped == 0 && (y.IsDisabled != "Y" || y.IsDisabled == null) && z.CreatedDate == x.CreatedDate
&& x.CreatedDate == Convert.ToDateTime(fromDate) && cpaclassids.Contains(x.ProductTypeId.ToString())
select x).Cast<PartJoinTable>().AsQueryable();
predicate = PredicateBuilder.True(query);
}
else
{
query = query.Join(partJoinTableRepository.GetPartJoinQuery(), "PartID", "PartID", "inner", "row1", null).Cast<PartJoinTable>().AsQueryable();
// predicate = PredicateBuilder.True(query);
} //query contains multiple dynamic inner joins
//repids contains the list ,I used the predicate builder for the linq to create AND Queries
foreach(var item in repids)
{
predicate = PredicateBuilder.True(query);
if (typeid == "3")
{
predicate = predicate.And(z => ids.Contains(z.ProductTypeId.ToString()) &&
z.CreatedDate == Convert.ToDateTime(fromDate));
}
}
var count = query.Where(predicate).Distinct().Count();
the above line is taking long time to execute,ids contains the lists and query contains the linq query.basically I need to form a multiple "AND" conditions
//The Query is taking lot of time to execute and multiple and conditions are not working
Remove ToList to improve performance. Because ToList execute your query and retrieve object list to memory. But you need only count. you don't need objects.
var count = query.Where(predicate).Distinct().Count();
If I understood you right, your problem is that this query has a long running time. Let's see your code in the last line:
var count = query.Where(predicate).Distinct().ToList().Count();
In LINQ to SQL (and to entities), your query doesn't execute thought you use ToList(), ToArray() etc.. For example, consider the following query:
var strings = Db.Table
.Where((string s) => s.Contains("A")) // Will convert to something like WHERE s LIKE '%A%'
.Select(s => s.ToUpper()) // Will convert to something like SELECT upper(s)
.ToList(); // Here the query sends to the DB and executes
The final query is SELECT upper(s) FROM [Table] WHERE s LIKE '%A%'.
In you case, first you send the query to the DB and get all the objects corresponding to the condition (.Where()), and then get their count inside your app.
Instead, if you'll get from the DB only the count, the query will be faster:
var count = query.Where(predicate).Distinct().Count(); // No .ToList()! Here, .Count() executes the query.

How to inject OR condition in Entity Framework linq query?

I have two and more if conditions to inject my main query but if the condition has no value (is nullable) I don't want to inject to my query.
For example this is my AND query injection:
// first initialize query can have where or not
var query = context.QuestionInfoes.Include(x =>x.RelationsInfoes).AsQueryable();
// first if condition to inject query
if (filterQuestionInfo.ToProfileId.HasValue)
{
query = (from q in query
join qr in context.QuestionRelationsInfoes on q.Id equals qr.QuestionId
where q.BrodcastType == QuestionBrodcastType.All || filterQuestionInfo.ToProfileId == qr.ToProfileId
select q);
}
// second if condition to inject AND query and i want to this be OR injection
if (filterQuestionInfo.ProfileId.HasValue)
{
query = (from q in query
where q.ProfileId == filterQuestionInfo.ProfileId
select q);
}
Now I want to create "OR" injection and when I call .ToList(), I see just queries in SQL that I needed. In top example if ToProfileId and ProfileId have values, I see questions where sent to ToProfileId value and 0 questions from profile id in "ProfileId" value because second query is "And" condition to first query. But I want both of them when I fill both values.
when two values are null: I filtered all of questions (works now)
when one value of ToProfileId or ProfileId is null: I filtered all of questions on that value is not null (works now)
When both value are filled, I want both question list (does not work now)
Note: I don't want to create one query and inject all of my condition in to that query.
Assuming QuestionRelationsInfoes exists on QuestionInfoes as a navigation property called QuestionRelationsInfoes, then you dont need the join.
Unfortunately, you will have to construct the query based on the 3 scenarios (Both filters set, only 1st filter set, only 2nd filter set).
var query = context.QuestionInfoes.Include(x => x.RelationsInfoes);
if (filterQuestionInfo.ToProfileId.HasValue && filterQuestionInfo.ProfileId.HasValue)
{
query = query.Where(q =>
q.BrodcastType == QuestionBrodcastType.All ||
q.QuestionRelationsInfoes.ToProfileId == filterQuestionInfo.ToProfileId ||
q.ProfileId == filterQuestionInfo.ProfileId);
}
else if (filterQuestionInfo.ToProfileId.HasValue)
{
query = query.Where(q => q.BrodcastType == QuestionBrodcastType.All || filterQuestionInfo.ToProfileId == q.QuestionRelationsInfoes.ToProfileId);
}
else if (filterQuestionInfo.ProfileId.HasValue)
{
query = query.Where(q.ProfileId == filterQuestionInfo.ProfileId);
}
As an alternative, if you dislike the code repetition, you can push the check to SQL, by first checking if the filter is not null:
var query = context.QuestionInfoes.Include(x => x.RelationsInfoes);
if (filterQuestionInfo.ToProfileId.HasValue || filterQuestionInfo.ProfileId.HasValue)
{
query = query.Where(q =>
(filterQuestionInfo.ToProfileId.HasValue && (q.BrodcastType == QuestionBrodcastType.All || filterQuestionInfo.ToProfileId == q.QuestionRelationsInfoes.ToProfileId)) ||
(filterQuestionInfo.ProfileId.HasValue && q.ProfileId == filterQuestionInfo.ProfileId));
}

Reduce linq query for filtering

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;

The method ‘Skip’ is only supported for sorted input in LINQ to Entities. The method ‘OrderBy’ must be called before the method ‘Skip’

Using Entity Framework 6.0.2 and .NET 4.5.1 in Visual Studio 2013 Update 1 with a DbContext connected to SQL Server:
I have a long chain of filters I am applying to a query based on the caller's desired results. Everything was fine until I needed to add paging. Here's a glimpse:
IQueryable<ProviderWithDistance> results = (from pl in db.ProviderLocations
let distance = pl.Location.Geocode.Distance(_geo)
where pl.Location.Geocode.IsEmpty == false
where distance <= radius * 1609.344
orderby distance
select new ProviderWithDistance() { Provider = pl.Provider, Distance = Math.Round((double)(distance / 1609.344), 1) }).Distinct();
if (gender != null)
{
results = results.Where(p => p.Provider.Gender == (gender.ToUpper() == "M" ? Gender.Male : Gender.Female));
}
if (type != null)
{
int providerType;
if (int.TryParse(type, out providerType))
results = results.Where(p => p.Provider.ProviderType.Id == providerType);
}
if (newpatients != null && newpatients == true)
{
results = results.Where(p => p.Provider.ProviderLocations.Any(pl => pl.AcceptingNewPatients == null || pl.AcceptingNewPatients == AcceptingNewPatients.Yes));
}
if (string.IsNullOrEmpty(specialties) == false)
{
List<int> _ids = specialties.Split(',').Select(int.Parse).ToList();
results = results.Where(p => p.Provider.Specialties.Any(x => _ids.Contains(x.Id)));
}
if (string.IsNullOrEmpty(degrees) == false)
{
List<int> _ids = specialties.Split(',').Select(int.Parse).ToList();
results = results.Where(p => p.Provider.Degrees.Any(x => _ids.Contains(x.Id)));
}
if (string.IsNullOrEmpty(languages) == false)
{
List<int> _ids = specialties.Split(',').Select(int.Parse).ToList();
results = results.Where(p => p.Provider.Languages.Any(x => _ids.Contains(x.Id)));
}
if (string.IsNullOrEmpty(keyword) == false)
{
results = results.Where(p =>
(p.Provider.FirstName + " " + p.Provider.LastName).Contains(keyword));
}
Here's the paging I added to the bottom (skip and max are just int parameters):
if (skip > 0)
results = results.Skip(skip);
results = results.Take(max);
return new ProviderWithDistanceDto { Locations = results.AsEnumerable() };
Now for my question(s):
As you can see, I am doing an orderby in the initial LINQ query, so why is it complaining that I need to do an OrderBy before doing a Skip (I thought I was?)...
I was under the assumption that it won't be turned into a SQL query and executed until I enumerate the results, which is why I wait until the last line to return the results AsEnumerable(). Is that the correct approach?
If I have to enumerate the results before doing Skip and Take how will that affect performance? Obviously I'd like to have SQL Server do the heavy lifting and return only the requested results. Or does it not matter (or have I got it wrong)?
I am doing an orderby in the initial LINQ query, so why is it complaining that I need to do an OrderBy before doing a Skip (I thought I was?)
Your result starts off correctly as an ordered queryable: the type returned from the query on the first line is IOrderedQueryable<ProviderWithDistance>, because you have an order by clause. However, adding a Where on top of it makes your query an ordinary IQueryable<ProviderWithDistance> again, causing the problem that you see down the road. Logically, that's the same thing, but the structure of the query definition in memory implies otherwise.
To fix this, remove the order by in the original query, and add it right before you are ready for the paging, like this:
...
if (string.IsNullOrEmpty(languages) == false)
...
if (string.IsNullOrEmpty(keyword) == false)
...
result = result.OrderBy(r => r.distance);
As long as ordering is the last operation, this should fix the runtime problem.
I was under the assumption that it won't be turned into a SQL query and executed until I enumerate the results, which is why I wait until the last line to return the results AsEnumerable(). Is that the correct approach?
Yes, that is the correct approach. You want your RDBMS to do as much work as possible, because doing paging in memory defeats the purpose of paging in the first place.
If I have to enumerate the results before doing Skip and Take how will that affect performance?
It would kill the performance, because your system would need to move around a lot more data than it did before you added paging.

Categories