LinQ Statement for filterings - c#

Im trying to filter out an ObservableCollection<MainBusinessObject> where I need to filter all items in the collection that have Subobject.PropertyX == true.
MainBusinessObject
- PropertyA int
- PropertyB string
- ListOfSubobject ObservableCollection<Subobject>
Subobject
- PropertyX bool
- PropertyY int
- PropertyZ - string
I really want to stay away from looping and if statements, but I can't seem to get the LinQ statements right. This is what I have so far:
return (MainBusinessObjectCollection)
listOfMainBusinessObject.Where(x =>
(x as MainBusinessObject).CanBePartitioned == true);
EDIT
I need to filter out the ListOfSubobject from the main business object

Depending if you want ANY sub-object to have that property or ALL sub-object to have that property:
var filteredList = listOfMainBusinessObject
.Where(x => x.ListOfSubobject.Any(s=>s.PropertyX));
or
var filteredList = listOfMainBusinessObject
.Where(x => x.ListOfSubobject.All(s=>s.PropertyX));
Also you have some casts that seem to be either invalid or unnecessary. To convert to a MainBusinessObjectCollection (assuming it is a collection of MainBusinessObjects), you're likely going to have to initialize it from the IEnumerable that Where returns:
var newList = new MainBusinessObjectCollection(filteredList);

If you want all Subobjects with Subobject.PropertyX == true:
listOfMainBusinessObject.SelectMany(x => x.ListOfSubobject)
.Where(x => x.PropertyX == true);

Related

Using Linq when getting value from nested list

I need to set a variable's value to the value of a property that is nested in several Lists. Each list only has one item except for one list. I'm trying to do this:
var myValue = myListA[0].myListB[0].myListC[0].
myListD.Where(x => x.Name == "Misc Expenses").myListE[0].price;
This produces a compile time error that says myListD does not contain a definition for myListE. What is the correct syntax?
After the .Where clause, you need to to .First() (or .ToList()) in order to apply the Where clause:
var myValue = myListA[0].myListB[0].myListC[0].
myListD.Where(x => x.Name == "Misc Expenses").First().myListE[0].price;
Technically, though, you can replace that .Where with .First directly, too:
var myValue = myListA[0].myListB[0].myListC[0].
myListD.First(x => x.Name == "Misc Expenses").myListE[0].price;

C# linq, get a list of distinct values of a object property, with null check in a groupby query

Hey I have a object being grouped to get a number of queries. For one of the properties SchoolInfoType I want to get list of all the distinct properties typeValues (property of SchoolInfoType) where typeName (property of schoolInfoType) = recess in a string list, but I can't seem to get it to work. School Info type may also be null so I also need to do a null check on properties. Final return type I want is List.
What I have so far, seems to be making a double enumerable, when I really only want 1.
Example code
var data = session.GroupBy(x => x.SchoolId).Select( a => new SchoolObject {
StudentHours = a.Sum(m => m.Hours),
// Query I tried but doesnt return correct return type end up getting embedded enumerables
// need the null check to prevent exception on comparing on null type, end result should
be list of unique type values matching where conditions
Types = a.Where( x => x.SchoolInfoType != null)
.Select( t => t.SchoolInfoType.Where(d => d.typeName == "recess")
.Select(n => n.TypeValue).Distinct))
}).ToList();
Structure of School Info Type Object relevant info
public class SchoolInfoType {
public string TypeName {get; set; }
public string TypeValue {get; set; }
}
The internal select is what is causing the nested output
Types = a.Where(x => x.SchoolInfoType != null)
.Select(x => x.SchoolInfoType)
.Where(x => x.typeName == "recess")
.Select(n => n.TypeValue)
.Distinct()
.ToList();
Your query is asking for IEnumerable of TypeValue's that is distinct and == recess, filtered from an IEnumerable that was filtered on nulls. This sort of nesting is unclear and is the cause of the end result being a nested IEnumerable. You are selecting within a select
A Select is going to produce you an IEnumerable, so selecting within another select is going to get you nesting. Not to mention the time and space complexity is going to suffer when it steps down the IL to nested loops

Unable to find nested property using LINQ

I don't understand why this is returning null.
dg.Depth = 3
My object looks like this
and this is returning null??
var x = dg.Children.FirstOrDefault(x => x.LeafPosition == dg.Depth);
Is my understanding wrong that LINQ would do this search recursively?
No, LINQ will not search recursively, you will need to flatten the nested collections somehow. dg.Children.FirstOrDefault(x => x.LeafPosition == dg.Depth) will search only the Children collection of dg not the descendants.
To search second level you can do something like this:
var secondLevelx = dg.Children
.SelectMany(x => x.Children)
.FirstOrDefault(x => x.LeafPosition == dg.Depth);
For third and so on you can add more chained SelectMany calls.
Looks like data structure wasn't designed for search of nested levels.
You can introduce a method for GroupLeafNode class
public class GroupLeafNode
{
public IEnumerable<GroupLeafNode> FindBy(int depth)
{
if (LeafPosition == depth)
{
yield return this;
}
return Children.SelectMany(node => node.FindBy(depth));
}
}
// Usage
var result =
dg.Children.SelectMany(node => node.FindBy(dg.Depth)).FirstOrDefault();

Linq with boolean function to relational db in Entity Framework

Probably a few things wrong with my code here but I'm mostly having a problem with the syntax. Entry is a model for use in Entries and contains a TimeStamp for each entry. Member is a model for people who are assigned entries and contains an fk for Entry. I want to sort my list of members based off of how many entries the member has within a given period (arbitrarily chose 30 days).
A. I'm not sure that the function I created works correctly, but this is aside from the main point because I haven't really dug into it yet.
B. I cannot figure out the syntax of the Linq statement or if it's even possible.
Function:
private bool TimeCompare(DateTime TimeStamp)
{
DateTime bound = DateTime.Today.AddDays(-30);
if (bound <= TimeStamp)
{
return true;
}
return false;
}
Member list:
public PartialViewResult List()
{
var query = repository.Members.OrderByDescending(p => p.Entry.Count).Where(TimeCompare(p => p.Entry.Select(e => e.TimeStamp));
//return PartialView(repository.Members);
return PartialView(query);
}
the var query is my problem here and I can't seem to find a way to incorporate a boolean function into a .where statement in a linq.
EDIT
To summarize I am simply trying to query all entries timestamped within the past 30 days.
I also have to emphasize the relational/fk part as that appears to be forcing the Timestamp to be IEnumerable of System.Datetime instead of simple System.Datetime.
This errors with "Cannot implicitly convert timestamp to bool" on the E.TimeStamp:
var query = repository.Members.Where(p => p.Entry.First(e => e.TimeStamp) <= past30).OrderByDescending(p => p.Entry.Count);
This errors with Operator '<=' cannot be applied to operands of type 'System.Collections.Generic.IEnumerable' and 'System.DateTime'
var query = repository.Members.Where(p => p.Entry.Select(e => e.TimeStamp) <= past30).OrderByDescending(p => p.Entry.Count);
EDIT2
Syntactically correct but not semantically:
var query = repository.Members.Where(p => p.Entry.Select(e => e.TimeStamp).FirstOrDefault() <= timeComparison).OrderByDescending(p => p.Entry.Count);
The desired result is to pull all members and then sort by the number of entries they have, this pulls members with entries and then orders by the number of entries they have. Essentially the .where should somehow be nested inside of the .count.
EDIT3
Syntactically correct but results in a runtime error (Exception Details: System.ArgumentException: DbSortClause expressions must have a type that is order comparable.
Parameter name: key):
var query = repository.Members.OrderByDescending(p => p.Entry.Where(e => e.TimeStamp <= timeComparison));
EDIT4
Closer (as this line compiles) but it doesn't seem to be having any effect on the object. Regardless of how many entries I add for a user it doesn't change the sort order as desired (or at all).
var timeComparison = DateTime.Today.AddDays(-30).Day;
var query = repository.Members.OrderByDescending(p => p.Entry.Select(e => e.TimeStamp.Day <= timeComparison).FirstOrDefault());
A bit of research dictates that Linq to Entities (IE: This section)
...var query = repository.Members.OrderByDescending(...
tends to really not like it if you use your own functions, since it will try to map to a SQL variant.
Try something along the lines of this, and see if it helps:
var query = repository.Members.AsEnumerable().Where(TimeCompare(p => p.Entry.Select(e => e.TimeStamp).OrderByDescending(p => p.Entry.Count));
Edit: I should just read what you are trying to do. You want it to grab only the ones within the last X number of days, correct? I believe the following should work, but I would need to test when I get to my home computer...
public PartialViewResult List()
{
var timeComparison = DateTime.Today.AddDays(-30);
var query = repository.Members.Where(p => p.Entry.Select(e => e.TimeStamp).FirstOrDefault() <= timeComparison).OrderByDescending(p => p.Entry.Count));
//return PartialView(repository.Members);
return PartialView(query);
}
Edit2: This may be a lack of understanding from your code, but is e the same type as p? If so, you should be able to just reference the timestamp like so:
public PartialViewResult List()
{
var timeComparison = DateTime.Today.AddDays(-30);
var query = repository.Members.Where(p => p.TimeStamp <= timeComparison).OrderByDescending(p => p.Entry.Count));
//return PartialView(repository.Members);
return PartialView(query);
}
Edit3: In Edit3, I see what you are trying to do now (I believe). You're close, but OrderByDescending would need to go on the end. Try this:
var query = repository.Members
.Select(p => p.Entry.Where(e => e.TimeStamp <= timeComparison))
.OrderByDescending(p => p.Entry.Count);
Thanks for all the help Dylan but here is the final answer:
public PartialViewResult List()
{
var timeComparison = DateTime.Today.AddDays(-30).Day;
var query = repository.Members
.OrderBy(m => m.Entry.Where(e => e.TimeStamp.Day <= timeComparison).Count());
return PartialView(query);
}

how to filter entity type framework object by its child object value properties?

I have an entity framework object called batch, this object has a 1 to many relationship to items.
so 1 batch has many items. and each item has many issues.
I want to filter the for batch items that have a certain issue code (x.code == issueNo).
I have written the following but Im getting this error:
items = batch.Select(b => b.Items
.Where(i => i.ItemOrganisations
.Select(o => o
.Issues.Select(x => x.Code == issueNo))));
Error 1:
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<System.Collections.Generic.IEnumerable<bool>>' to 'bool'
Error 2:
Cannot convert lambda expression to delegate type 'System.Func<Ebiquity.Reputation.Neptune.Model.Item,bool>' because some of the return types in the block are not implicitly convertible to the delegate return type
Select extension method needs a lambda expression that returns a boolean, but the inner o.Issues.Select returns an IEnumerable of boolean to the outer Select(o => o which result in the exception you're getting.
Try using Any instead which verifies that at least one element verifies the condition:
items = batch.Select(
b => b.Items.Where(
i => i.ItemOrganisations.Any(
o => o.Issues.Any(x => x.Code == issueNo)
)
)
);
If I understand correctly, you're trying to select through multiple layers of enumerables. In those cases you need SelectMany which flattens out the layers, not Select. LINQ's syntax sugar is made specifically to make SelectMany easier to reason about:
var items = from item in batch.Items
from org in item.ItemOrganizations
from issue in org.Issues
where issue.Code == issueNo
select item;
The compiler translates that into something like this:
var items = batch.Items
.SelectMany(item => item.ItemOrganizations, (item, org) => new {item, org})
.SelectMany(#t => #t.org.Issues, (#t, issue) => new {#t, issue})
.Where(#t => #t.issue.Code == issueNo)
.Select(#t => #t.#t.item);
You can always wrap this in a Distinct if you need to avoid duplicate items:
var items = (from item in batch.Items
from org in item.ItemOrganizations
from issue in org.Issues
where issue.Code == issueNo
select item).Distinct();
It's hard to tell what you're trying to do based on your code but I think you're looking for something like this;
var issue = batch.Select(b => b.Items).Select(i => i.Issues).Where(x => x.Code == issueNo).Select(x => x).FirstOrDefault();
The above query will return the first issue where the Issues Code property is equal to issueNo. If no such issue exists it will return null.
One problem (the cause of your first error) in your query is that you're using select like it's a where clause at the end of your query. Select is used to project an argument, when you do Select(x => x.Code == issueNo) what you're doing is projecting x.Code to a bool, the value returned by that select is the result of x.Code == issueNo, it seems like you want that condition in a where clause and then you want to return the issue which satisfies it which is what my query is doing.
items = from b in batch.Include("Items")
where b.Items.Any(x=>x.Code==issueNo)
select b;
You're getting lost in lambdas. Your LINQ chains are all embedded in each other, making it harder to reason about. I'd recommend some helper functions here:
static bool HasIssueWithCode(this ItemOrganization org, int issueNo)
{
return org.Issues.Any(issue => issue.Code == issueNo);
}
static bool HasIssueWithCode(this Item items, int issueNo)
{
return items.ItemOrganizations.Any(org => org.HasIssueWithCode(issueNo));
}
Then your answer is simply and obviously
var items = batch.Items.Where(item => item.HasIssueWithCode(issueNo));
If you inline these functions, the result is the exact same as manji's (so give manji credit for the correct answer), but I think it's a bit easier to read.

Categories