Unable to find nested property using LINQ - c#

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

Related

Can I use Strongly type parameters based on properties from Object

I have a list of objects.
I have a single item with same type as the list.
I would like to create a generic extension method (called Find) to find an item from the list based on the single obj as well as an arbitrary list of its strongly typed properties.
Here is how I would like to call the method:
var obj = new SomeObject() { ... } ;
var list = new List<SomeObject>() { ... };
// Find similar objects
list.Find(obj, x => x.Id,y => y.Description);
Is this arrangement possible?
FirstOrDefault would work as in the comment below. However, I am looking for a way to use the pattern in different scenarios that might not be a simple lookup.
You can do this
public static T Find<T>(this IEnumerable<T> source, params Func<T,bool>[] condition)
{
return source.FirstOrDefault(o => condition.All(f => f(o))); // use All for && or use Any for ||
}
And to use it
var item = list.Find(x=> x.Id == obj.Id,x=> x.Description == obj.Description);
You can use Object if you want to make this work more generally by casting properties to object. but this will be a bit slower by the way and you must note that the Equals method for custom types must be overriden in order to make it work.
public static T Find<T>(this IEnumerable<T> source, T obj, params Func<T, object>[] condition)
{
return source.FirstOrDefault(o => condition.All(f => f(o).Equals(f(obj))));
}
Then you can call it exactly like this.
var item = list.Find(obj, x => x.Id,y => y.Description);
You can use Where if you want to return all similar items. by just changing FirstOrDefault into Where and the return type of method into IEnumerable<T>
It doesn't seem to me like you're saving a lot of time/effort versus just using a normal Where clause that filters on whichever properties you're after.
var findResults = list.Where(x => x.Id == obj.Id || x.Description == obj.Description);
Maybe you should consider proceeding like this:
var myResult = from currentObject in mylist
where currentObject == mySingleObject // change it by your matching criterias linked by && or || (as in if ...)
// NB: you can also override the == operator in your object definition
// but as mentionned below, it won't work for generic types
select currentObject;

LINQ- to retrieve the third layer list of objects from a list which contains three layers

I have a class structure like in the following.
CategoryA
{
CategoryB
{
CategoryC
{
}
}
}
I have a LINQ query to get a list of objects.
var ls = CategoryA.CategoryB.Where(x => x.CategoryBObjectId == someId);
This returns a list of CategoryB objects.
And then I want some CategoryC objects.
var ls2= ls.Where(x=>x.Any(y=>y.CategoryCObjectId==someAnotherID))
But what ls2 returns is CategoryB objects.
What I want is a list of CategoryC objects fulfilling the condition specified.
Anyone who can help me to change the LINQ query accordingly ?
Use SelectMany.
var data = CategoryA.CategoryB
.Where(b => b.CategoryBObjectId == someBid)
.SelectMany(b => b.CategoryC)
.Where(c => c.CategoryCObjectId == someCid)
.ToList();
it looks like you already get categor c when you get category b
so instead search like this i think
var ls2= ls.Where(x=>x.Any(y=>y.CategoryC.CategoryCObjectId==someAnotherID))
There is issue in your second query, there you again filtering values from categoryB.
Your second query need to go like this.
var ls2= ls.CategoryC.Where(x => x.CategoryBObjectId == someId);

LinQ Statement for filterings

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

Return a List with multiple results using LINQ and EF

I have this simple method that returns a user:
User usr = ReliableExecution.RetryWithExpression<User, User>(u => u.FirstOrDefault(x => x.UserEmail == userEmail));
Now I need to create a similar method, but I need to return a list
List<Asset> lst = ReliableExecution.RetryWithExpression<Asset, List<Asset>>(u => u.SelectMany(x => x.EventId == eventId));
My problem is with the [SelectMany(x => x.EventId == eventId)] part that doesn't compile and I can't understand exactly how to use LINQ to get multiple results.
I have specified "SelectMany" just for an example, it can be whatever you find correct.
This is the signature of RetryWithExpression for reference:
public static TValue RetryWithExpression<T, TValue>(Func<ObjectSet<T>, TValue> func, Int32 retryInfiniteLoopGuard = 0)
where T : class
I think your expression should be rewritten as follows:
List<Asset> lst = ReliableExecution
.RetryWithExpression<Asset, List<Asset>>(
u => u.Where(x => x.EventId == eventId).ToList()
);
In simple terms, SelectMany flattens a "list of lists of items A" into a "list of items B" using a functor that extracts a list of items B from each single item A; this is not what you want to do.
It seems like you want:
List<Asset> lst = ReliableExecution.RetryWithExpression<Asset, List<Asset>>
(u => u.SelectMany(x => x.Where(y => y.EventId == eventId)));
SelectMany expects the passed Func to return an IEnumerable, which it then flattens. You were passing through a list of Asset and then trying to select the EventId directly on the list. What you really wanted was to select all Assets in the list with matching EventId, hence the extra Where clause.

How to use System.Linq.Expressions.Expression to filter based on children?

I have a filter that I use across many methods:
Expression<Func<Child, bool>> filter = child => child.Status == 1;
(actually is more complex than that)
And I have to do the following
return db.Parents.Where(parent => parent.Status == 1 &&
parent.Child.Status == 1);
where the condition is the same as in the filter above.
I want to reuse the filter in this method. But I don't know how. I tried
return db.Parents.Where(parent => parent.Status == 1 &&
filter(parent.Child));
but an Expression can't be used as a method
If you want to combine expressions and still be able to use linq-to-sql, you may want to have a look at LinqKit. It walks inside your expression and replaces all the function calls by their contents before the sql conversion.
This way you'll be able to use directly
return db.Parents
.AsExpandable()
.Where(parent => parent.Status == 1 && filter(parent.Child));
You can try this:
var compiledFilter = filter.Compile();
foreach (var parent in db.Parents.Where(parent => parent.Status == 1))
if (compiledFilter(parent.Child))
yield return parent;
It requires you to pull all of the parents, but unlike #HugoRune's solution, it doesn't require a 1:1 relation of Parent:Child.
I don't think this will be useful for your situation because of the different types involved, but just in case, here is an example of how you can combine Expressions: How do I combine LINQ expressions into one?
Edit: I had previously suggested using Compile(), but that doesn't work over LINQ-to-SQL.
Well, if there is a 1:1 relationship between parent and child
(unlikely, but the example seems to imply that) then you could do it like this:
return db.Parents
.Where(parent => parent.Status == 1)
.Select(parent => parent.Child)
.Where(filter)
.Select(child=> child.Parent);
Otherwise it will be hard.
You could do it with dynamic linq but that is probably overkill.
You could generate your expression tree manually, but that is also quite complicated. I have not tried that myself.
As a last resort you could of course always call yourQuery.AsEnumerable(), this will cause linq-to-sql to translate your query into sql up to this point and perform the rest of the work on the client-side; then you can .compile() your expression. However you lose the performance benefits of linq-to-sql (and compile() itself is quite slow; whenever it is executed, it calls the JIT-compiler):
return db.Parents
.Where(parent => parent.Status == 1)
.AsEnumerable()
.Where(parent => filter.Compile().Invoke(parent.Child))
Personally I'd just define the expression twice, once for child and once for parent.child:
Expression<Func<Child, bool>> filterChild = child => child.Status == 1;
Expression<Func<Parent, bool>> filterParent = parent => parent.Child.Status == 1;
Might not be the most elegant, but probably easier to maintain than the other solutions
Just come up with this, check if this would work for you
public interface IStatus { public int Status { get; set; } }
public class Child : IStatus { }
public class Parent : IStatus
{public Child Child { get; set; } }
Func<IStatus, bool> filter = (x) => x.Status == 1;
var list = Parents.Where(parent => filter(parent) && filter(parent.Child));
Hope this helps!
Could you just use the expression as a function instead?
Instead of:
Expression<Func<Child, bool>> filter = child => child.Status == 1;
Use that same expression as a generic function this way:
Func<Child, bool> filter = child => child.Status == 1;
Then you will be able to use the function in just the same way you were trying to use an expression:
return db.Parents.Where(parent => parent.Status == 1 &&
filter(parent.Child));
Edit: I misunderstood the question. This is a bad answer. 6+ years out, I'm still getting comments to the effect that this doesn't work. I'm not sure, from a hygiene perspective, if it would be better to just delete the answer, or add this edit and let the answer stand as an example of something that decidedly doesn't work. I'm open to advisement on that.
There's no need for external libraries or mucking around with expression trees. Instead, write your lambda functions to use query chaining and take advantage of LINQ's deferred execution.
Instead of:
Expression<Func<Child, bool>> filter = child => child.Status == 1;
Rewrite it as:
Func<IQueryable<Parent>, IQueryable<Parent>> applyFilterOnParent = query => query.Where(parent => parent.Child.Status == 1);
Func<IQueryable<Child>, IQueryable<Child>> applyFilterOnChild = query => query.Where(child => child.Status == 1);
Now, instead of:
return db.Parents.Where(parent => parent.Status == 1 &&
filter(parent.Child));
You can write:
var query = db.Parents.AsQueryable();
query = applyFilterOnParent(query);
return query.Where(parent => parent.Status == 1);
And you can re-use the applyFilter functions in other LINQ queries. This technique works well when you want to use lambda functions together with LINQ-to-SQL, because LINQ will not translate a lambda function to SQL.

Categories