LINQ: grouping based on property in sublist - c#

I'am trying to use LINQ to create a grouped list of documents based on metadata which is a list on the document.
Below is how my object structure looks:
List<Document>
--> List<Metadata>
--> Metadata has a name and a value property.
I want to group the documents based on an metadata tag which has a name: ID and group them where the values for the ID property are the same.
I tried it like this:
var x = response.Document
.GroupBy(d => d.Metadata.Where(dc => dc.Name == DocProperty.ID)
.Select(dc => dc.Value));
This results in a list of single documents, but not grouped on ID.
Also thought about selecting a distinct list of ID's and then loop through the document list and find documents that match the ID. That one seems like a lot of overhead, because for every ID in the distinct list i have to go every time into the metadata list and find the documents and have to extra checks for multiple items found, get the property i need etc.
Anyone has a good idea about how to get this thing working?

var x = from doc in source
from meta in doc.Metadata
where meta.Name == DocProperty.Id
group doc by meta.Value;
Or (comments) as fluent notation:
var y = source
.SelectMany(doc => doc.Metadata, (doc, meta) => new { doc, meta })
.Where(pair => pair.meta.Name == DocProperty.Id)
.GroupBy(pair => pair.meta.Value, pair => pair.doc);

Related

Getting null data when executing LINQ to Entities query

I have the following Book table:
From this table, I am trying to get the latest registrationNumber based on the group ID as an input from the user.
So, my query looks like this at the moment:
var booksQuery = _context.Books.Where(g => g.GroupId == id)
.OrderByDescending(g => g.RegistrationNumber).GroupBy(g => g.GroupId);
id is the group Id specified by the user. So for example, if id = 15, then I should get the 15:6 as my latest registration number. To do that, I basically grouped by id and ordered the result by descending order. But that is giving me null results. Anyone know why? I am very new to this LINQ-Entitiy coding.
As mentioned by others you really should make your registrationNumber field an integer since you are wanting to sort on it. In the event, you can't make the change, below is a Linq query that basically parses the registration number and converts to an integer to sort on the first and second part by splitting at the colon. This works for sorting when you have 15:10, etc, as in the string sort 15:6 comes before 15:10
var booksQuery = books.Where(g => g.GroupId == id).ToList();
var bookWanted = booksQuery
.OrderByDescending(g => int.Parse(g.registrationNumber.Split(':')[0]))
.ThenByDescending(g=> int.Parse(g.registrationNumber.Split(':')[1]))
.FirstOrDefault();

Lambda Distinct not working

I am unable to get a distinct list of 'Order' from my Lambda query. Even though am using the keyword Distinct() it is still returning repeated select list item.
public ActionResult Index()
{
var query = _dbContext.Orders
.ToList()
.Select(x => new SelectListItem
{
Text = x.OrderID.ToString(),
Value = x.ShipCity
})
.OrderBy(y => y.Value)
.Distinct();
ViewBag.DropDownValues = new SelectList(query, "Text", "Value");
return View();
}
Any suggestions please?
UPDATE
Sorry guys I genuinely missed out the Distinct() from my code. I have now added it to my code.
I am basically trying to get all distinct rows where yes the values are same but the ids are different.
Same as this SQL Query......
SELECT distinct [ShipCity] FROM [northwind].[dbo].[Orders] ORDER by ShipCity
I'm assuming you removed your distinct from the end of the query.
Actually for that matter i don't see how you could get duplicate orders at all since you're doing nothing in your query except selecting and your query is on a table in a database, so you already can't get the same row multiple time.
What do you call a "duplicate"? If you mean two rows with the same values except their ID that's not a duplicate at all, that's just two unrelated rows, with the same values . . .
If on the other hand you mean you expect them to be equal because you're tossing the .Distinct after the select and you're only using OrderId and ShipCity in there for which there are duplicates (and i really don't see why a column named OrderId in an orders table should have duplicates but that's another issue) then that still won't work because you're NOT selecting OrderId nor ShipCity, you're selecting a new SelectListItem and if you create two reference types with the same value, they're not equal in .NET, they need to be the same instance to be equal, not two instances with different values.
edited following your comment :
var query = _dbContext.Orders
.ToList()
// Group them by what you want to "distint" on
.GroupBy(item=>item.ShipCity)
// For each of those groups grab the first item, we just faked a distinct)
.Select(item=>item.First())
.Select(x => new SelectListItem
{
Text = x.OrderID.ToString(),
Value = x.ShipCity
})
.OrderBy(y => y.Value)
.Distinct();

Getting records in between two records using Linq

I have a list of objects and need to get the list of records from this list. like I have of Countries and I need to get the list of countries which are in between country with name "Australia" and country "Indonasia", the list will not be sorted.
Am using c#.
I tried to use something like, get the index of first and second and then use that to get the list with a for loop, but would be handy if it can be done in single query.
If you do the following:
var elementsBetween = allElements
.SkipWhile(c => c.Name != "Australia")
.Skip(1) // otherwise we'd get Australia too
.TakeWhile(c => c.Name != "Indonasia");
you'll get the result you want without iterating through the list 3 times.
(This is assuming your countries are e.g. Country items with a Name string property.)
Note that this doesn't sort the countries at all - it's unclear from your question whether you want this or not but it's trivial to add an OrderBy before the SkipWhile.
this should do the job
var query = data.SkipWhile(x => x != "Australia").TakeWhile(x => x != "Indonesia")

How to Group and Order in a LINQ Query

I would like to group & order by in a query builder expression. The following query gets me close to what i want but the order by does not appear to be working.
what i have is an object that has unique ids but some will have a common versionId. I would like to get the last edited item of the same versionId. So only one item per version id and i want it to be the last edited one.
IQueryable<Item> result = DataContext.Items.Where(x => (x.ItemName.Contains(searchKeyword) ||
x.ItemDescription.Contains(searchKeyword))
.GroupBy(y => y.VersionId)
.Select(z => z.OrderByDescending(item => item.LastModifiedDateTime).FirstOrDefault());
Edit: I don't really care about the order of the result set, i really just care about what item within the grouping is returned. I want to make sure that the last edited Item within a versionId group is return.
Your z parameter contains the individual group objects.
By calling OrderBy inside of Select, you're ordering the items in each group, but not the groups themselves.
You need to also call OrderBy after Select, like this:
.Select(z.OrderByDescending(item => item.LastModifiedDateTime).FirstOrDefault())
.Where(item => item != null)
.OrderByDescending(item => item.LastModifiedTime)

How do I group these XDocuments?

Problem
I have a collection of XDocument instances. Each document has a repeated element that can take a different value. I want to group them by this value but each element can specify a different value.
<sampledoc>
<value>a</value>
<value>b</value>
<value>c</value>
</sampledoc>
Example
Document A has values a, b, c
Document B has values b, c, d
Document C has values a, b
I want a grouping that is:
group a
Document A
Document C
group b
Document A
Document B
Document C
group c
Document A
Document B
group d
Document B
Question
I'm sure I must be able to do this but I can't see the wood for the trees right now.
docs.GroupBy... won't work on it's own (as far as I can tell) because the expression it takes should return a single value, and each document can contain multiple values. My head says a single LINQ query should be possible, but it can't fathom what it would be.
Can this be done using the GroupBy or AsLookup LINQ methods? Is there a way to do this?
I'd prefer examples in C# if anyone would be willing to provide one.
Update
Thanks to the answer from Pavel Minaev and a little inspiration, I solved this as follows:
// Collate all the different values
docs.SelectMany(doc => doc.Elements("Value")
.Select(el => el.Value))
// Remove duplicate values
.Distinct()
// Generate a lookup of specific value to all
// documents that contain that value
.ToLookup(v => v, v => docs.Where(doc => doc.Elements("Value")
.Any(el=>el.Value == v)));
GroupBy won't help you here anyway, because it assigns every element in the sequence to only one group.
var docs = new XDocument[] { docA, docB, docC } ;
var result = docs
.SelectMany(doc => doc.Root.Elements("Value"))
.Select(el => el.Value)
.Distinct()
.Select(key => new {
Key = key,
Documents = docs.Where(doc =>
doc.Root.Elements("Value").Any(el => el.Value == key))
});

Categories

Resources