I'm here to ask a question on how would a code look like when it came to advanced filtering in C# with LINQ. I have experience with Linq, but this is something that was out of my understanding.
Lets say we have a class Item that has properties (string)Name, (bool)New and (int)Price and users would have to input their filters and get the results they need.
Lets say we put 5 objects inside a list list that is a List of Items.
new Item("Pen",true,12);
new Item("PostIt",false,35);
new Item("Phone",true,140);
new Item("Watch",true,5);
new Item("Lavalamp",false,2);
Now I woud like to process this information to get.. All New times that cost over 10. I know I can do this with
List<Item> Results = list.where(item => item.Price> 10 && item.New).ToList();
but what if a user wants to get all items that cost over 10 regardless of being new or not.. I can't change the query during runtime to fit the needs and I don't think that making a query for every possible combination of input parameters is a right way to do this... Can someone give me an example on how this should be done?
You can define base query
var result = list.Where(item=> item.Price > 10); //DON'T Call ToList() here
if(someCondition)
result = result.Where(item=> item.New);
//in the end you are calling
return result.ToList();
Like #MikeEason said you don't want to call ToList() on your first result because this will execute the query. Your goal is to build the complex query and execute it only once. Because of that this is done when you return the result.
If you only have those three conditions then you can build your query in several steps:
IEnumerable<Item> result=list;
int Price=10;
bool FilterByPrice, bool FilterByNew;//Set this variables in your environment
if(FilterByPrice)
result=result.Where(item => item.Price> Price);
if(FilterByNew)
result=result.Where(item => item.New);
Your query will be executed when you call ToList method or went you iterate over the query result thanks to deferred execution.
So let's say your items exist in your database and you want to query them. The user has a checkbox, if he wants to see only new items or all of them. If the box is checked you set a bool value for it.
// Compose the query
var results = _db.Where(item => item.Price > 10 );
// Still composing
if (onlyNewItems)
{
results = results.Where(item => item.New);
}
// ToList() executes the query, data is returned;
return results.ToList();
This does not run the query twice. In fact, until you materialize your query, you are still composing it. If you would return it now, it would be of type IQueryable<T>. Only after you call .ToList(), is your query actually executed and you get an IEnumerable<T> in this case a List<T> back.
List<Item> Results = list.where(item => item.Price > 10
&& (condition ? item.New : true)).ToList();
you can extend this way. just pass true if your condition is false and it is like nothing is happened.
I have a collection of data = IEnumerable<AnalyticsData> and I'm trying to group by multiple properties and Sum() on an integer column. The end result will be a collection of AnalyticsReportRow<dynamic>() as you can see below, though this isn't highly relevant.
In the final Select() method, I want to pass an object in, ideally from the original set and would prefer not to recreate one in the middle of my chained queries if possible. Most of the examples seem like the create either a new strongly-typed or dynamic object to pass into the next link in the chain.
Here's what I have spent a few hours trying to work with, and this returns the set as it is in the first code block below with all rows (I export to CSV, hence the formatting):
var pageViewsData = analyticsData.GroupBy(data => new { g1 = data.Webproperty, pv = data.PageViews, d = data })
.GroupBy(data => new { gg1 = data.Key.g1, dd = data.Key.d })
.Select(data => new AnalyticsReportRow<dynamic>(data.Key.dd, "Page_Views", data.Sum(datas => datas.Key.pv)));
Result is this:
"CustomerA","","","","","Page_Views",0,"A1-810","","",2,"4/10/2015 16:08:33"
"CustomerA","","","","","Page_Views",0,"A1-810","","",2,"4/10/2015 16:08:33"
"CustomerA","","","","","Page_Views",0,"GT-N8013","","",2,"4/10/2015 16:08:33"
"CustomerA","","","","","Page_Views",0,"GT-P3113","","",7,"4/10/2015 16:08:33"
"CustomerA","","","","","Page_Views",0,"GT-P3113","","",2,"4/10/2015 16:08:33"
"CustomerA","","","","","Page_Views",0,"GT-P3113","","",3,"4/10/2015 16:08:33"
"CustomerA","","","","","Page_Views",0,"GT-P3113","","",3,"4/10/2015 16:08:33"
"CustomerA","","","","","Page_Views",0,"GT-P3113","","",2,"4/10/2015 16:08:33"
And would like to end up with a Sum() on the second-last column, grouped by customer and then by device. For example:
"CustomerA","","","","","Page_Views",0,"A1-810","","",4,"4/10/2015 16:08:33"
"CustomerA","","","","","Page_Views",0,"GT-N8013","","",2,"4/10/2015 16:08:33"
"CustomerA","","","","","Page_Views",0,"GT-P3113","","",16,"4/10/2015 16:08:33"
I am having a hard time wrapping my head around the logic and could really use an example of how to group like this, even pseudocode and dynamic types.
Thank you.
After spending several hours on this today, and posting my question, I decided to try a few more things and read some more documentation on GroupBy().
As it turns out, I was missing the fact that you can provide a Key Selector and an Element Selector to the GroupBy method as explained in the MSDN documentation. If I understand correctly, this provides the ability to have a distinct qualifier that tells the query how to group.
In the end, this appears to give me what I need. I would really like some feedback on this to make sure I'm going about it correctly:
var pageViewsData = analyticsData.Where(data => data.PageViews > 0)
.GroupBy(data => new { g1 = data.Webproperty, g2 = data.DeviceModel }, data => data)
.Select(data => new AnalyticsReportRow<dynamic>(data.FirstOrDefault(), "Page_Views", data.Sum(d => d.PageViews)));
Try something like this:
var query = from d in analyticsData
group d by new { d.Webproperty, d.DeviceModel }
into g
select new
{
g.Webproperty,
g.DeviceModel,
Total = g.Sum(it => it.PageViews)
};
var result = query.ToList();
I have an array of ProgramIDs and would like to create a number of Select statements dynamically depending on how many ProgramIds there are.
For example:
var surveyProgramVar = surveyProgramRepository.Find().Where(x => x.ProgramId == resultsviewmodel.ProgramIds.FirstOrDefault());
This is an example of the select statement working with a single ProgramId.FirstOrDefault(). How do I create a list/array of SurveyProgramVars and select for each ProgramIds in the array?
It won't be necessarily optimal, but you might try:
var surveyProgramVar = surveyProgramRepository.Find()
.Where(x => resultsviewmodel.ProgramIds.Contains(x.ProgramId));
You could try something like:
var surveyProgramVar = surveyProgramRepository.Find().Where(x => resultsviewmodel.ProgramIds.Contains(x.ProgramId));
Tip: If the Find() method does a hit on a database, would be nice if you create a specific method to to a IN statment on the query. If you does not do this, it will take all records on a table and filter it in memory (linq to objects), which works but not very nice. Your code could be something like:
var surveyProgramVar = surveyProgramRepository.FindByProgramsId(resultsviewmodel.ProgramIds);
I have a query pulling from a database:
List<myClass> items = new List<myClass>(from i in context
select new myClass
{
A = i.A,
B = "", // i doesn't know this, this comes from elsewhere
C = i.C
}
I also have another query doing a similar thing:
List<myClass2> otherItems = new List<myClass2>(from j in context
select new myClass2
{
A = j.A, // A is the intersection, there will only be 1 A here but many A's in items
B = j.B
}
In reality these classes are much larger and query data that is separated not only by database but by server as well. Is it possible to use a LINQ query to populate the property B for all items where items.A intersect? All of the built in LINQ predicates appear only to do aggregates, selections or bool expressions.
In my brain I had something like this, but this is all off:
items.Where(x => x.B = (otherItems.Where(z => z.A == x.A).Single().B));
Or am I being ridiculous with trying to make this work in LINQ and should just abandon it in favor of a for loop where the actual setting becomes trivial? Because of deadlines I will be resorting to the for loop (and it's probably going to end up being a lot more readable in the long run anyway), but is it possible to do this? Would an extension method be necessary to add a special predicate to allow this?
LINQ is designed for querying. If you're trying to set things, you should definitely use a loop (probably foreach). That doesn't mean you won't be able to use LINQ as part of that loop, but you shouldn't be trying to apply a side-effect within LINQ itself.
Query the OtherItems first. Do a ToDictionary() on the result. Then, when querying the database, do this:
var items = from i in context
select new myClass
{ A = i.A,
B = otherItems[i.A],
C = i.C
}
I have the following method:
public string GetDepartmentTitle(string DepartmentAbbreviation) {
List<TaxonomyItem> Divisions = TaxonomyFromCMS.GetAllItems(DomainDataConstants.DivisionAndDepartment.TAXONOMY_ID);
List<TaxonomyItem> Departments = new List<TaxonomyItem>();
Divisions.ForEach(delegate(TaxonomyItem Division) {
Departments.AddRange(Division.SubTaxonomyItems);
});
TaxonomyItem Result = (from d in Departments
where d.Name == DepartmentAbbreviation
select d).FirstOrDefault();
return Result == null ? "" : Result.Title;
}
It first reads all of the Divisons (which there are only 3) but those divisions have many Departments below them as SubTaxonomyItems. Currently I step through each of the Divisions and extract out each of the Departments and put them in a List called Departments. Then I use Linq to search for the specific item.
It works great but I would love to skip/consume that first step of getting the sub items. I have tried the following line that doesn't seem to work:
TaxonomyItem Result = (from d in Departments.SubTaxonomyItems
I then through perhaps some sort of lambda with a foreach of the Departments.SubTaxonomyItems that contains a yeild statement. That may be the trick, but I couldn't get it to work. Looking into the yeild statement it seems there can be a way if I make some extension method. But I am wanting to see if it can be done inline and like the following pseudo code:
public string GetDepartmentTitle(string DepartmentAbbreviation) {
List<TaxonomyItem> Divisions = TaxonomyFromCMS.GetAllItems(DomainDataConstants.DivisionAndDepartment.TAXONOMY_ID);
TaxonomyItem Result = (from d in Divisions.ForEach(delegate(TaxonomyItem Division) {
yeild return Divison.SubTaxonomyItems;
}) AS Dps
where Dps.Name == DepartmentAbbreviation
select Dps).FirstOrDefault();
return Result == null ? "" : Result.Title;
}
Is this possible this way or some other way I am not seeing? Can it even be done without an extension method?
First off, you can solve your problem easily by just adding another "from" to the query:
var query = from division in divisions
from department in division.Departments
where department.Name == whatever
select department;
This does exactly what you were doing; it selects out the sequence of departments from each division, and glues all those sequences together to make one long sequence of departments.
This gives you a slick syntax for the "stitch together a bunch of sequences" scenario. More generally though, sometimes you run into this sort of situation:
var bars = from foo in foos
some complicated query logic here
select foo.bar;
var abcs = from bar in bars
some other query logic here
select bar.abc;
and you want to figure out how to make this into a single query. You can do that like this:
var abcs = from bar in (
from foo in foos
some complicated query logic here
select foo.bar)
some other query logic here
select bar.abc;
which is ugly, or you can do this:
var abcs = from foo in foos
some complicated query logic here
select foo.bar into bar
some other query logic here
select bar.abc;
That does exactly the same thing but it is more pleasant to read. This syntax is called a "query continuation".
To answer your specific question: it is not legal to put a "yield return" in an anonymous method or lambda. This is quite unfortunate because it would be really useful. The transformations that the compiler performs to make anonymous functions and iterator blocks work are quite complex and thus far we have always punted on getting them to work together fully. (That is, you can put a lambda in an iterator block, but you can't put an iterator block in a lambda.) I hope, but do not promise, that some day we'll be able to fix this code up and allow iterator block lambdas. (Remember, Eric's musings about future language features are For Entertainment Purposes Only.)
It looks like you just want something like this.
public string GetDepartmentTitle(string DepartmentAbbreviation) {
var items = TaxonomyFromCMS.GetAllItems(DomainDataConstants.DivisionAndDepartment.TAXONOMY_ID);
var result = items.SelectMany(item=>item.SubTaxonomyItems).FirstOrDefault(item=>item.Name == DepartmentAbbreviation);
var text = result !=null ? result.Title : String.Empty;
return text;
}
Yield return can only be used in very select (pun!) locations, and a Linq query isn't one of them. Luckily you don't need it here.
var q = from division in Divisions
from dps in division.SubTaxonomyItems
where dps.Name == DepartmentAbbreviation
select dps.Title;
return q.FirstOrDefault() ?? String.Empty;
Why not just do:
var divisions = TaxonomyFromCMS.GetAllItems
(DomainDataConstants.DivisionAndDepartment.TAXONOMY_ID);
var titles = from division in divisions
from deparment in division.SubTaxonomyItems
where deparment.Name == DepartmentAbbreviation
select deparment.Title;
return titles.FirstorDefault() ?? "";
Is this linq you are looking for?
var Result = Divisions.SelectMany(d => d.SubTaxonomyItems).Where(subItem => subItem.Name == DepartmentAbbreviation).FirstOrDefault();