Using Linq to query nested dynamic objects - c#

I have used the following linq query to access the member Names. The following successfully returns all member Name values within the collection.
var we = CsQ.Groups.SelectMany(g => g.Members).Where(a => a.Name == "Name").Select(b => b.Value).ToList();
I want to now filter this down based another property within "Members" called AbsoluteUri which is nested in a property AgentsByUri. This doesnt work but gives an idea of the structure:
var uri = CsQ.Groups.SelectMany(g => g.Members).Where(a => a.Name == "AgentsByUri").Select(b => b.Value).//??? I NEED TO NOW ACCESS "AbsoluteUri"
How can I combine these in to one query so that I can return only the "Names" that have an "AbsoluteUri" that contains "SomeValue". AbsoluteUri seems to be nested in a collection thats nested in AgentsByUri which adds to the complication.
You can see the structure of the AgentsByUri object here - Using C# Linq to query nested objects
Excuse my terminology, I'm reasonably new to C#! Hopefully this makes sense :)
Any help or guidance VERY appreciate :)
EDIT3
Getting somewhere! Casting as dynamic partially working - member.AgentsByUri is now OK, just cant figure out how to make it apply to the rest of the query. Tried adding in various locations by no luck.
EDIT2
Thanks for everyone's input. I haven't had any further success. I think the biggest problem is that I am dealing with a PowerShell object which is dynamically generated at run time. As a result I cannot access the classes/object because the compiler doesn't yet no about them. To get around this I use the "dynamic" type which allows the compiler to trust that what I provide will be valid at run time. Can I cast as dynamic in a linq query? Or do I need to go about this in a different way?
Heres what I get with the examples give:
EDIT1 (click and zoom, image is high res):

I'm just writing this down, based on the image you provided. However, I can not check for correctness without the surrounding code, so no guarantees.
var uri = CsQ.Groups
.SelectMany(g => g.Members)
.Where(m => m.AgentsByUri.SelectMany(a => a.Value, (a, v) => v.AbsoluteUri).Contains("SomeValue"))
.Select(b => b.Value)
In LINQ syntax it should be much clearer
var uri =
from group in CsQ.Groups
from dynamic member in group.Members
where
(from agent in (IEnumerable<dynamic>)member.AgentsByUri
where agent.Name = "AgentsByUri" // this line may be redundant
from x in (IEnumerable<dynamic>)agent.Value
select x.AbsoluteUri).Contains("SomeValue")
select member.Value;
EDIT: The suggestion in my second comment does not quite work. I changed the code in LINQ syntax above to account for dynamic objects as the source by explicitly casting to IEnumerable<dynamic>. Note however that the cast will fail for an enumeration of a value type.
I hope this will work for you.

It's hard to give a proper answer without having a proper overview of the class structure. I guess this might work:
var uri = CsQ.Groups.SelectMany(g => g.Members).Where(a => a.Name == "AgentsByUri").Select(b => b.Value).Where(x => x.AbsoluteUri == "SomeValue");

I had to do some digging and make a few assumptions here:
You want to return a list of PSMemberInfo.
PsMemberInfo.Name must equal "AgentsByUri".
In the Value property (which is a collection of Uri), you want to filter this collection on items whose AbsoluteUri property equals "SomeValue".
var we = CsQ.Groups.SelectMany(g => g.Members).Where(member => member.Name == "AgentsByUri" && member.Value != null && member.Value.Any(uri => uri.AbsoluteUri == "SomeValue")).ToList();

Related

Refer to a Property name using a Variable in Linq

I am trying to access a property "DailyQtyOrders_01" in my object using the first code below, this works ok.
but now i want to use it in a linq expression on the second line code, this works, but i want the property to be variable (3rd line code), this throws error. What is the method to do that?
item.GetType().GetProperty("DailyQtyOrders_01").SetValue(item, 5);
productsSales.Where(x => x.sellerId == item.sellerId).FirstOrDefault().DailyQtyOrders_01 = 5;
productsSales.Where(x => x.sellerId == item.sellerId).FirstOrDefault().item.GetType().GetProperty("DailyQtyOrders_01").SetValue(item, 5);
First of all if this code is run in any sort of loop, you have to be careful you dont run into performance issues.
When you enter the areas of .GetType(),.GetProperty() and .SetProperty() you are doing reflection, which really shouldn't be nessesary.
It's hard to see from your code example but the Order that you have in your item should really be changable from item.DailyQtyOrders_01 directly. Also if you really want to use strings and reflection, which i cannot recommend in any way, you can at least use strongly typed ways to achieve this (which makes sure your code doesn't break on a refactor)
That would make this code:
item.GetType().GetProperty("DailyQtyOrders_01").SetValue(item, 5)
Look like this instead:
item.GetType().GetProperty(nameof(Order.DailyQtyOrders_01)).SetValue(item, 5)
available from c# 6.0 and forward, nameof can be used on classes and properties and so on to get the string, but avoiding the actual string, that you might not catch in a rename.
The problem lies in the last part of your third linq statement.
productsSales.Where(x => x.sellerId == item.sellerId).FirstOrDefault().item.GetType().GetProperty("DailyQtyOrders_01").SetValue(item, 5);
In the above code, you are assigning the value of "DailyQtyOrders_01" in an instance of "item". This is not the same instance of your linq query refers to, but the variable "item" you have created/referred above.
.GetProperty("DailyQtyOrders_01").SetValue(item, 5);
What you need to do would be to split the query into two parts, and refer the instance on which you want the value to be set.
For example
var itemInstance = productsSales.Where(x => x.sellerId == item.sellerId).FirstOrDefault().item;
itemInstance.GetType().GetProperty("DailyQtyOrders_01").SetValue(itemInstance, 5);
productsSales.Where(x => x.sellerId == item.sellerId).FirstOrDefault().item = itemInstance;

How to get first parent node that has a boolean property in Umbraco 7

In a partial view macro, I'm trying to get the first ancestor or self node that contains a property called "breakInheritance" and has the value of True. I've been attacking this statement / query for about an hour now while hunting around SO and the Our.Umbraco forums but I'm afraid I'm not getting anywhere. I feel like this should be pretty straight forward.
Query
var nodeToUse = CurrentPage.AncestorOrSelf(x => (x.HasProperty("breakInheritance") && x.GetPropertyValue<bool>("breakInheritance")));
The lambda expression is underlined in red and says - Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type
I've found tons of resources on this error, but in all the cases that I've seen, it's someones custom extension method that they can just edit, so it hasn't really helped me too much.
I'd say it's better not to mix the strongly typed API with the dynamic API. In your code, you can either do CurrentPage.AncestorsOrSelf or Model.Content.AncestorsOrSelf(), with the first example clearly not accepting a lambda expression, as the error message indicates.
Try the following instead:
var node = Model.Content.AncestorsOrSelf()
.FirstOrDefault(n => n.HasProperty("breakInheritance") &&
n.GetPropertyValue<bool>("breakInheritance"))
I was able to get the desired behavior by casting the AncestorsOrSelf Method to IEnumerable<IPublishedContent>
var nodeToUse = ((IEnumerable<IPublishedContent>)CurrentPage
.AncestorsOrSelf())
.Where(x => (x.HasProperty("breakInheritance") && x.GetPropertyValue<bool>("breakInheritance") && x.HasValue("widgets")))
.FirstOrDefault();

Converting for-loop to a linq query

I like to think of myself as pretty good with LINQ, but every now and then I try to accomplish something that I just can't get to work. I'd like to convert a SPListItemCollection to a dictionary so I can use the key to look up a value without the need for a LINQ query each time:
var formsConfigItems = new Dictionary<string, string>();
foreach (SPListItem item in list.GetItems(query))
formsConfigItems.Add(item.Title, (item["Value"] == null ? string.Empty : item["Value"].ToString()));
This works, but I was hoping to do it in a cleaner fashion, using LINQ. (not a big deal but I like to use LINQ over for-loops whenever possible, although it's the same thing behind the scenes.
I tried to do something like this:
var formsConfigItems = (from SPListItem i in list.GetItems(query)
select new { i.Title, i["Value"].ToString() }).ToDictionary<string, string>(k=>k.Key, k=>k.Value);
But that doesn't seem to work. If I try to use a lambda expression on list.GetItems(query), I'm not given the option to use .Where or any LINQ commands (which is weird because it is an SPListCollection)
Thanks in advance.
Try:
var formsConfigItems = list.GetItems(query)
.Cast<SPListItem>()
.ToDictionary(item => item.Title,
item => Convert.ToString(item["Value"]));
To answer your queries:
If I try to use a lambda expression on list.GetItems(query), I'm not
given the option to use .Where or any linq commands (which is weird
because it is an SPListCollection)
That's because SPListCollection is an "old-school" collection that implements IEnumerable but not IEnumerable<T>, so C# / LINQ (at compile-time anyway) can't tell what type of items it contains. The Cast<SPListItem>() call helps work around this issue - it turns an IEnumerable into an IEnumerable<T>, allowing the type-algebra to work out at compile-time . Your for loop doesn't have this issue since you explicitly specify the type of the loop variable - the compiler inserts a cast on your behalf for each item in the sequence.
I tried to do something like this (query expression). But that
doesn't seem to work.
That's because you are not constructing the anonymous type instance correctly (property names can't be inferred for arbitrary expressions) and your lambda expression isn't quite right either (the property names you use don't match the property names of the anonymous type). Try this instead:
var formsConfigItems = (from SPListItem i in list.GetItems(query)
select new
{
i.Title,
Value = Convert.ToString(i["Value"])
}).ToDictionary(a => a.Title, a => a.Value);
Ani's got the better solution IMO, but one other thing you're missing: Your LINQ statement is creating a collection of anonymous items, but you're not giving names to the properties in that anonymous class.
The k=>k.Key expression doesn't work, because it doesn't know what Key is - you've only defined Title (since you didn't give it a name, it borrowed the one from the object). The Value one can't be automatically figured out, so it would throw a compiler error.
To do it this way, you'd need to specifically declare the names:
new { Key = i.Title, Value = i["Value"].ToString() }

How to remove an element from an IGrouping

How do I remove an object directly from an IGrouping IGrouping<DateTime, VMAppointment>?
The only way I know of currently is to generate a new IGrouping without the concering element, but I don't like this way because it causes some trouble within my application.
Any ideas?
No, there's no way to mutate an IGrouping<,>, at least in general - and even if you knew the concrete type, I don't believe any of the implementations exposed by the .NET framework allow the group to be mutated.
Presumably the grouping is the result of some query - so if possible, change the original query to exclude the values you aren't interested in.
I know this is old question, but hopefully this helps someone else. A workaround for this is to cast the group to a list, then use the values from the list instead of the group.
var groups = someList.GroupBy(x => x...);
foreach (var group in groups)
{
var groupList = group.ToList();
...
groupList.Remove(someItem);
//Process the other code from groupList.
}
You could cast using Select and use TakeWhile if you have a testable condition (such as null as in the example) on a property in your group:
var removedItemsList = group.Select(x => x.TakeWhile(t => t.someProperty != null));
This will return an IEnumerable<IEnumerable<YourGroup>>.

DbSortClause expressions must have a type that is order comparable parameter Name :Key

I am using Linq to entity and have the following query
IQueryable<DomainModel.User> userResult =
userResult.OrderBy(u => u.UserClientRoles.OrderBy(r => r.Role.RoleName));
But I am getting this error
DbSortClause expressions must have a type that is order comparable
parameter Name :Key
and it returns an empty collection.
Any idea what's going on?
.OrderBy(), when working with databases, is supposed to take in a delegate that returns only a single property that represents a column in your database. I'm not sure what you're trying to do, but it looks like
u.UserClientRoles.OrderBy(r => r.Role.RoleName)
Will return an enumeration of values, which can't be ordered.
I had the same problem, I solved it using this:
your code:
IQueryable<DomainModel.User> userResult = userResult.OrderBy(u => u.UserClientRoles.OrderBy(r => r.Role.RoleName));
my code:
List<Membership> results = new List<Membership>();
results.AddRange(memberships.OrderBy(m => m.Roles));
memberships = results.AsQueryable();
coincidences:
*.OrderBy(m => m.Roles)
solution:
*.OrderBy(m => m.Roles.Select(r => r.RoleId).FirstOrDefault())
possible problem's reason:
Maybe, you did what I did, and cause that 1 user/member could have more than 1 role in the same membership. That made a conflict with/to OrderBy() because the application can just "order" a single element at the time, when she call the Role (which is an ICollection of elements) the instead receive more than 1 element with no kind of priority's levels (even when we could assume that the application will take the role's index as priority's base level, actually its don't).
solution's explaination:
When you add the *.Select(r => r.RoleId), you are specifying to the application which element will be used to OrderBy(). But, as you shall see when you maybe reached at this point, just by using the *.Select(r => r.RoleId) could be not enough, because the application is still receiving multiple results with the same priority's level. Adding *.Select(r => r.RoleId).FirstOrDefault() you are basically saying: "...I don't care how many results you received from that element, just the focus on the first result, or order them by its default..." (default normally means EMPTY or NULL).
additional information:
I used non-official's simple concepts/meanings to explain a complex solution with simple words, which means that you could maybe have problems to find similar posts in the web by using the words/concepts used in this "answer". Otherwise, the code itself works and you shouldn't not have any problem by applying it and/or modifying it by yourself. GOOD LUCK!!! (^_^)
In my case, I was accidentally trying to order by an object instead of ordering by one of it's properties.
You should you use
var query = from Foo in Bar
orderby Foo.PropertyName
select Foo;
Instead of
var query = from Foo in Bar
orderby Foo
select Foo;
Note: you will get the same behaviour event if there is an override on Foo's ToString() method.

Categories

Resources