Linq dynamically adding where conditions - c#

I have a gridview which has drop down boxes in each header for filtering. Each filter is loaded with the distinct values from its column when loaded. At run time, I add "ALL" to allow the user to select all from that field. I am trying to build the linq statement dynamically to ignore the field if the drop down box is set to "ALL". Is this possible? I want to see if I can do this in one single statement. The example below only shows 2 dropdown boxes, but my actually case has up to 5.
If I choose to use if then statements, I end up with spaghetti code.
DropDownList drpOwners = this.grdOtherQuotes.HeaderRow.FindControl("drpOwners") as DropDownList;
DropDownList drpCompanyName = this.grdOtherQuotes.HeaderRow.FindControl("drpCompanyName") as DropDownList;
var filteredList = (from x in allQuotes
where (drpOwners.SelectedValue != ALL) ? x.SalesRepFullName == drpOwners.SelectedValue:true
&& drpCompanyName.SelectedValue != ALL ? x.CompanyName == drpCompanyName.SelectedValue: true
select x);

Personally, I'd find having this broken up to be simpler:
IEnumerable<Quote> filteredList = allQuotes;
// If using EF or LINQ to SQL, use: IQueryable<Quote> filteredList = allQuotes;
if (drpOwners.SelectedValue != ALL)
filteredList = filteredList.Where(x => x.SalesRepFullName == drpOwners.SelectedValue);
if (drpCompanyName.SelectedValue != ALL)
filteredList = filteredList.Where(x => x.CompanyName == drpCompanyName.SelectedValue);
// More conditions as needed
This really isn't any longer, and it's far simpler to follow.
If you really wanted to be able to write this as a "one-liner", you could make an extension method to build the query. For example, if using Entity Framework:
static IQueryable<T> AddCondition(this IQueryable<T> queryable, Func<bool> predicate, Expression<Func<T,bool>> filter)
{
if (predicate())
return queryable.Where(filter);
else
return queryable;
}
This would then let you write this as:
var filteredList = allQuotes
.AddCondition(() => drpOwners.SelectedValue != ALL, x => x.SalesRepFullName == drpOwners.SelectedValue)
.AddCondition(() => drpCompanyName.SelectedValue != ALL, x.CompanyName == drpCompanyName.SelectedValue);
You could, of course, take this even further, and make a version that hard-wires the predicate to check a combo box against "ALL", making the predicate shorter (just the combo box).

You could create a helper method that handles the All logic. Something like:
private bool CompareSelectedValue(string value, string dropDownValue)
{
if(dropDownValue == "ALL")
return true;
else
return value == dropDownValue;
}
Then your query could be:
var filteredList = (from x in allQuotes
where (CompareSelectedValue(x.SalesRepFullName, drpOwners.SelectedValue)
&& CompareSelectedValue(x.CompanyName, drpCompanyName.SelectedValue)
select x);

Create extension method(s) which encapsulate the where logic so it looks cleaner:
var filteredList = allQuotes.WhereDropOwnersAreContained()
.WhereCompanyIsContained()
...
;

Related

Build where clause body

I'm trying to find a way to build a where clause and pass it to repository Get() method. It is supposed to filter items which name starts with a specific letter. I was able to construct part of this Where Clause Body, but can't find a way how to handle the scenario where the item name does not start with letter. For example: _ItemName or 97_SomeName.
So, here is my method:
protected override Expression<Func<DataSetSettings, bool>> GetWhereClause()
{
//The user has selected FilterLetter, for example: "A"
// return all items which name starts with "A"
if (!string.IsNullOrWhiteSpace(FilterLetter) && !FilterLetter.Equals("All"))
return (x => x.Name.StartsWith(FilterLetter) && x.Type == Type);
if (FilterLetter.Equals("Other"))
{
//Here i need to extract all items which name does not start with letter
}
//return All items of the current type
return x => x.Type == Type;
}
I would appreciate any help! Thanks!
Now that I understand what you need, I looked around and was not able to find a graceful solution to this. Seems complex string pattern matching is a weak point in EF.
The only way I can see to do this is to either compare against every letter, ie:
!x.Name.StartsWith("A") && !x.Name.StartsWith("B") && //on and on to Z
Or to make sure the entire list is loaded into memory and then use regular expressions to filter:
protected override Expression<Func<DataSetSettings, bool>> GetWhereClause()
{
var noletter = new Regex("^[^a-z].*", RegexOptions.IgnoreCase);
return (
x => x.Type == Type && (
string.IsNullOrWhiteSpace(FilterLetter) ||
FilterLetter == "All" ||
(FilterType == "Other" && noletter.IsMatch(x.Name)) ||
x.Name.StartsWith(FilterType)
)
);
}
If you do end up going with option of loading everything into memory, you can at least filter based on the x.Type first. That seems to be the common denominator in the filtering. At least that way you don't have to load the entire table into memory.
You can do equals false on your StartsWith
Like this:
return (x => x.Name.StartsWith(FilterLetter) == false && x.Type == Type);
My advice would be make 1 db call of all types into a list, then use linq to query that list. Your example would be making two db calls.
List<Type> allTypes = new List<Type>();
List<Type> typesWithA = new List<Type>();
List<Type> typesWOA = new List<Type>();
// make one db call
allTypes = entities.Types.ToList();
typesWithA = allTypes.Where(x => x.Name.StartsWith(FilterLetter)).ToList();
typesWOA = allTypes.Where(x => !x.Name.StartsWith(FilterLetter)).ToList();

Linq: how to exclude condition if parameter is null

I have some table and the following condition of query: if parameter A is null take all, if not, use it in the query. I know how to do that in 2 steps:
List<O> list = null;
if (A = null)
{
list = context.Obj.Select(o => o).ToList();
}
else
{
list = context.Obj.Where(o.A == A).ToList();
}
Is it possible to have the same as one query?
Thanks
How about:
list = context.Obj.Where(o => A == null || o.A == A)
.ToList();
You can do it in one query but still using a condition:
IEnumerable<O> query = context.Obj;
if (A != null)
{
query = query.Where(o => o.A == A);
}
var list = query.ToList();
Or you could use a conditional operator to put the query in a single statement:
var query = A is null ? context.Obj : context.Obj.Where(o => o.A == A);
var list = query.ToList();
I would personally suggest either of the latter options, as they don't require that the LINQ provider is able to optimise away the filter in the case where A is null. (I'd expect most good LINQ providers / databases to be able to do that, but I'd generally avoid specifying a filter when it's not needed.)
I opted for
var list = context.Obj.Where(o => A.HasValue ? o.a == A : true);
I would probably write the query like this:
IQueryable<O> query = context.Obj;
if (A != null)
query = query.Where(o => o.A == A);
var list = query.ToList()
It's not one expression, but I think it's quite readable.
Also, this code assumes that context.Obj is IQueryable<O> (e.g. you are using LINQ to SQL). If that's not the case, just use IEnumerable<O>.

Conditional Linq Queries

I have a dropdownlist which when selected pulls the data out of a database. There are many options in the dropdownlist and one of them is "All". I want that when the user selects the "All" option it should pull everything out of the database. What is a good way to implement this feature?
With LINQ you can easily modify a query before you send it to the database:
IQueryable<Item> query = dataContext.Items;
if (selectedText != "All")
{
query = query.Where(item => item.Type == selectedText);
}
List<Item> result = query.ToList();
Alternatively you can write it in a single query:
IQueryable<Item> query = dataContext.Items
.Where(item => selectedText == "All" || item.Type == selectedText);
Check the value, and only perform the Where statement if not "All".
var linqQuery = ...
if (selectedValue != "All")
linqQuery = linqQuery.Where(w => w.Value == selectedValue);
If you are dynamically building your queries a lot, then you might want to have a look at one of the really cool Linq samples, "the Dynamic Linq library". Scott Guthrie has a nice blog post about it.
Edit: Note in this specific case because you are on the right side of the where clause, you don't need to be completely dynamic and this would be overkill, but if you have a situation where you are also filtering dynamically, then.....

LINQ: How to remove element from IQueryable<T>

How do you loop through IQueryable and remove some elements I don't need.
I am looking for something like this
var items = MyDataContext.Items.Where(x => x.Container.ID == myContainerId);
foreach(Item item in items)
{
if(IsNotWhatINeed(item))
items.Remove(item);
}
Is it possible? Thanks in advance
You should be able to query that further as in this
var filtered = items.Where(itm => IsWhatINeed(itm));
Also notice the subtle change in the boolean function to an affirmative rather than a negative. That (the negative) is what the not operator is for.
items = items.Where( x => !IsNotWhatINeed(x) );
var items = MyDataContext.Items.Where(x => x.Container.ID == myContainerId
&& !IsNotWhatINeed(x));
or
var items = MyDataContext.Items.Where(x => x.Container.ID == myContainerId)
.Where(x=> !IsNotWhatINeed(x));
The other answers are correct in that you can further refine the query with a 'where' statement. However, I'm assuming your query is a Linq2Sql query. So you need to make sure you have the data in memory before further filtering with a custom function:
var items = MyDataContext.Items.Where(x => x.Container.ID == myContainerId)
.ToList(); // fetch the data in memory
var itemsToRemove = items.Where(IsNotWhatINeed);
If you really want to extend the IQueryable, then the 'IsNotWhatINeed' function must be translated to something that Linq2Sql understands.
Try This:
var items = YourDataContext.Items.Where(x => x.Container.ID == myContainerId
&& !IsNotWhatYouNeed(x));

Dynamic WHERE clause in LINQ

What is the best way to assemble a dynamic WHERE clause to a LINQ statement?
I have several dozen checkboxes on a form and am passing them back as: Dictionary<string, List<string>> (Dictionary<fieldName,List<values>>) to my LINQ query.
public IOrderedQueryable<ProductDetail> GetProductList(string productGroupName, string productTypeName, Dictionary<string,List<string>> filterDictionary)
{
var q = from c in db.ProductDetail
where c.ProductGroupName == productGroupName && c.ProductTypeName == productTypeName
// insert dynamic filter here
orderby c.ProductTypeName
select c;
return q;
}
(source: scottgu.com)
You need something like this? Use the Linq Dynamic Query Library (download includes examples).
Check out ScottGu's blog for more examples.
I have similar scenario where I need to add filters based on the user input and I chain the where clause.
Here is the sample code.
var votes = db.Votes.Where(r => r.SurveyID == surveyId);
if (fromDate != null)
{
votes = votes.Where(r => r.VoteDate.Value >= fromDate);
}
if (toDate != null)
{
votes = votes.Where(r => r.VoteDate.Value <= toDate);
}
votes = votes.Take(LimitRows).OrderByDescending(r => r.VoteDate);
You can also use the PredicateBuilder from LinqKit to chain multiple typesafe lambda expressions using Or or And.
http://www.albahari.com/nutshell/predicatebuilder.aspx
A simple Approach can be if your Columns are of Simple Type like String
public static IEnumerable<MyObject> WhereQuery(IEnumerable<MyObject> source, string columnName, string propertyValue)
{
return source.Where(m => { return m.GetType().GetProperty(columnName).GetValue(m, null).ToString().StartsWith(propertyValue); });
}
It seems much simpler and simpler to use the ternary operator to decide dynamically if a condition is included
List productList = new List();
productList =
db.ProductDetail.Where(p => p.ProductDetailID > 0 //Example prop
&& (String.IsNullOrEmpty(iproductGroupName) ? (true):(p.iproductGroupName.Equals(iproductGroupName)) ) //use ternary operator to make the condition dynamic
&& (ID == 0 ? (true) : (p.ID == IDParam))
).ToList();
I came up with a solution that even I can understand... by using the 'Contains' method you can chain as many WHERE's as you like. If the WHERE is an empty string, it's ignored (or evaluated as a select all). Here is my example of joining 2 tables in LINQ, applying multiple where clauses and populating a model class to be returned to the view. (this is a select all).
public ActionResult Index()
{
string AssetGroupCode = "";
string StatusCode = "";
string SearchString = "";
var mdl = from a in _db.Assets
join t in _db.Tags on a.ASSETID equals t.ASSETID
where a.ASSETGROUPCODE.Contains(AssetGroupCode)
&& a.STATUSCODE.Contains(StatusCode)
&& (
a.PO.Contains(SearchString)
|| a.MODEL.Contains(SearchString)
|| a.USERNAME.Contains(SearchString)
|| a.LOCATION.Contains(SearchString)
|| t.TAGNUMBER.Contains(SearchString)
|| t.SERIALNUMBER.Contains(SearchString)
)
select new AssetListView
{
AssetId = a.ASSETID,
TagId = t.TAGID,
PO = a.PO,
Model = a.MODEL,
UserName = a.USERNAME,
Location = a.LOCATION,
Tag = t.TAGNUMBER,
SerialNum = t.SERIALNUMBER
};
return View(mdl);
}
Just to share my idea for this case.
Another approach by solution is:
public IOrderedQueryable GetProductList(string productGroupName, string productTypeName, Dictionary> filterDictionary)
{
return db.ProductDetail
.where
(
p =>
(
(String.IsNullOrEmpty(productGroupName) || c.ProductGroupName.Contains(productGroupName))
&& (String.IsNullOrEmpty(productTypeName) || c.ProductTypeName.Contains(productTypeName))
// Apply similar logic to filterDictionary parameter here !!!
)
);
}
This approach is very flexible and allow with any parameter to be nullable.
You could use the Any() extension method. The following seems to work for me.
XStreamingElement root = new XStreamingElement("Results",
from el in StreamProductItem(file)
where fieldsToSearch.Any(s => el.Element(s) != null && el.Element(s).Value.Contains(searchTerm))
select fieldsToReturn.Select(r => (r == "product") ? el : el.Element(r))
);
Console.WriteLine(root.ToString());
Where 'fieldsToSearch' and 'fieldsToReturn' are both List objects.
This is the solution I came up with if anyone is interested.
https://kellyschronicles.wordpress.com/2017/12/16/dynamic-predicate-for-a-linq-query/
First we identify the single element type we need to use ( Of TRow As DataRow) and then identify the “source” we are using and tie the identifier to that source ((source As TypedTableBase(Of TRow)). Then we must specify the predicate, or the WHERE clause that is going to be passed (predicate As Func(Of TRow, Boolean)) which will either be returned as true or false. Then we identify how we want the returned information ordered (OrderByField As String). Our function will then return a EnumerableRowCollection(Of TRow), our collection of datarows that have met the conditions of our predicate(EnumerableRowCollection(Of TRow)). This is a basic example. Of course you must make sure your order field doesn’t contain nulls, or have handled that situation properly and make sure your column names (if you are using a strongly typed datasource never mind this, it will rename the columns for you) are standard.
System.Linq.Dynamic might help you build LINQ expressions at runtime.
The dynamic query library relies on a simple expression language for formulating expressions and queries in strings.
It provides you with string-based extension methods that you can pass any string expression into instead of using language operators or type-safe lambda extension methods.
It is simple and easy to use and is particularly useful in scenarios where queries are entirely dynamic, and you want to provide an end-user UI to help build them.
Source: Overview in Dynamic LINQ
The library lets you create LINQ expressions from plain strings, therefore, giving you the possibility to dynamically build a LINQ expression concatenating strings as you require.
Here's an example of what can be achieved:
var resultDynamic = context.Customers
.Where("City == #0 and Age > #1", "Paris", 50)
.ToList();

Categories

Resources