LINQ - Preserve order across objects with .Any()? [duplicate] - c#

This question already has answers here:
List sort based on another list
(3 answers)
Closed 9 years ago.
I am building a search function which needs to return a list ordered by relevance.
IList<ProjectDTO> projects = new List<ProjectDTO>();
projects = GetSomeProjects();
List<ProjectDTO> rawSearchResults = new List<ProjectDTO>();
//<snip> - do the various search functions here and write to the rawSearchResults
//now take the raw list of projects and group them into project number and
//number of search returns.
//we will sort by number of search returns and then last updated date
var orderedProjects = rawSearchResults.GroupBy(x => x.ProjectNbr)
.Select(x => new
{
Count = x.Count(),
ProjectNbr = x.Key,
LastUpdated = x.First().UpdatedDateTime
})
.OrderByDescending(x => x.Count)
.ThenByDescending(x => x.LastUpdated);
So far so good; the "orderedProjects" variable returns my list in the correct order. However, I need the entire object for the next step. When I try to query back to get the original object type, my results lose their order. In retrospect, this makes sense, but I need to find a way around it.
projects = (from p in projects
where orderedProjects.Any(o => o.ProjectNbr == p.ProjectNbr)
select p).ToList();
Is there a LINQ-friendly method for preserving the order in the above projects query?
I can loop through the orderedProject list and get each item, but that's not very efficient. I can also rebuild the entire object in the original orderedProjects query, but I'd like to avoid that if possible.

You need to do it the other way around:
Query orderedProjects and select the corresponding items from projects:
var projects =
orderedProjects
.Select(o => projects.SingleOrDefault(p => p.ProjectNbr == o.ProjectNbr))
.Where(x => x != null) // This is only necessary if there can be
// ProjectNbrs in orderedProjects that are not in
// projects
.ToList();

You shouldn't use "Select" in the middle there as that operator transforms the object into another type and you say that you need the original object.
var orderedProjects = rawSearchResults.GroupBy(x => x.ProjectNbr)
.OrderByDescending(x => x.Count)
.ThenByDescending(x => x.First().UpdatedDateTime);
Do they come in chronological order or something? Otherwise, I'm pretty sure you want the "ThenByDescending" to be performed on the newest or oldest project update like so:
var orderedProjects = rawSearchResults.GroupBy(x => x.ProjectNbr)
.OrderByDescending(x => x.Count)
.ThenByDescending(x => x.Max(p=>p.UpdatedDateTime));

Related

C# LINQ Filter deep nested list

I've a structure based of list containing other list. I need to filter the list based on the value of a property based in the deepest part of the list.
Right now I'm doing this:
queryable = queryable
.Include(x => x.Carriers)
.ThenInclude(c => c.CarrierActions)
.ThenInclude(ca => ca.Action)
.ThenInclude(ac => ac.ActionFacts);
queryable = queryable
.Where(x => x.Carriers.Any(
carriers => carriers.CarrierActions.Any(
carrieractions =>
carrieractions.Action.ActionTypeId ==
ActionTypeEnum.DosemeterCalculateDeepDose)));
I join the needed tables, then I filter them based on the ActionTypeId based 3 levels below the top list.
First off all, is it possible to do this in 1 step ( include the filtering with the joins ), second of all, the second part is not working as my list gets empty, but I'm certain that actions with that type get values.
Using .NET Core 2.0.3 btw!
To answer your first part, you can do this
queryable = queryable
.Include(x => x.Carriers)
.ThenInclude(c => c.CarrierActions)
.ThenInclude(ca => ca.Action)
.ThenInclude(ac => ac.ActionFacts)
.Where(x => x.Carriers.Any(
carriers => carriers.CarrierActions.Any(
carrieractions =>
carrieractions.Action.ActionTypeId ==
ActionTypeEnum.DosemeterCalculateDeepDose)))
To your second part, it should be working, id check your data, as this is pretty straight forward

C# change from groupby

Suppose there are two properties in Myclass: Date, Symbol
I want to frequently convert between those two properties, but I find that
for List <Myclass> vector
if I use
vector.groupby(o => o.Date).Select(o => o)
the vector is no longer the type of List<IGrouping<string, Myclass>>
And if I want to convert groupby(o => o.Date) to groupby(o => o.Symbol)
I have to use
vector.groupby(o => o.Date).Selectmany(o => o).groupby(o => o.Symbol)
I try to use SortedList<Date, Myclass>, but I am not familiar with SortedList(actually, I don't know what's the difference between SortedList and Groupby).
Is there any effective way to achieve such effect, as I highly depend on the speed of running?
int volDay = 100;
Datetime today = new DateTime(2012, 1, 1);
//choose the effective database used today, that is the symbol with data more than volDay
var todayData = dataBase.Where(o => o.Date <= today).OrderByDescending(o => o.Date)
.GroupBy(o => o.Symbol).Select(o => o.Take(volDay))
.Where(o => o.Count() == volDay).SelectMany(o => o);
//Select symbols we want today
var symbolList = todayData
.Where(o => o.Date == today && o.Eqy_Dvd_Yld_12M > 0))
.OrderByDescending(o => o.CUR_MKT_CAP)
.Take((int)(1.5 * volDay)).Where(o => o.Close > o.DMA10)
.OrderBy(o => o.AnnualizedVolatility10)
.Take(volDay).Select(o => o.Symbol).ToList();
//Select the database again only for the symbols in symbolList
var portfolios = todayData.GroupBy(o => o.Symbol)
.Where(o=>symbolList.Contains(o.Key)).ToList();
This is my real code, dataBase is the total data, and I will run the cycle day by day(here just given a fixed day). The last List portfolios is the final goal I want obtain, you can ignore other properties, which are used for the selections under the collection of Date and Symbol
It may be faster, or at least easier to read, if you performed a .Distinct().
To get distinct Dates:
var distinctDates = vector.Select(o => o.Date).Distinct()
To get distinct Symbols:
var distinctSymbols = vector.Select(o => o.Symbol).Distinct()
I asked what you were trying to accomplish so that I can provide you with a useful answer. Do you need both values together? E.g., the unique set of symbols and dates? You should only need a single group by statement depending on what you are ultimately trying to achieve.
E.g., this question Group By Multiple Columns would be relevant if you want to group by multiple properties and track the two unique pieces of data. a .Distinct() after the grouping should still work.

Cannot implicitly convert type 'System.Collections.Generic.List<System.Collections.Generic.IEnumerable<xxx>>' to 'System.Collections.Generic.List<xxx>

I am getting the following error. I googled it for more than a day but I cant find the exact solution, Please help me Thank you
ERROR: Cannot implicitly convert type
System.Collections.Generic.List<System.Collections.Generic.IEnumerable<ADCO.eJMC.EntityDataModel.ShareholderUser>>
to
System.Collections.Generic.List<ADCO.eJMC.EntityDataModel.ShareholderUser>
I used the following code
List<ShareholderUser> list = new List<ShareholderUser>();
list = dataContext.EJMCShareholderApprovals
.Include(s => s.Shareholder.ShareholderUsers)
.Where(e => e.EJMCRequestId == requestId)
.Select(s => s.Shareholder.ShareholderUsers
.Where(x => x.AccessMode == true))
.ToList();
The problem is that at the moment, you're selecting a sequence of sequences - one sequence of ShareholderUser items for each Shareholder. If you just want a list of ShareholderUser items, you need to flatten the results. That's most easily done using SelectMany, which can actually replace your Select call in this case.
List<ShareholderUser> list = dataContext.EJMCShareholderApprovals
.Where(e => e.EJMCRequestId == requestId)
.SelectMany(s => s.Shareholder.ShareholderUsers)
.Where(x => x.AccessMode == true)
.ToList();
Note how breaking the query over multiple lines makes it much simpler to read, too. Also, there's no point in initializing the list variable to a new List<ShareholderUser> if you're then immediately going to give it a different value. I've also removed the Include call, as that was unnecessary - you're explicitly selecting Shareholder.ShareholderUsers in the query, so you don't need to include it.
This should do it?
var list = dataContext.EJMCShareholderApprovals
.Include(s => s.Shareholder.ShareholderUsers)
.Where(e => e.EJMCRequestId == requestId)
.Select(s => s.Shareholder.ShareholderUsers
.Where(x => x.AccessMode == true)).ToList();
though you are doing select on to ShareHolderUsers? Are you trying to get a list of ShareHolderUsers or a list of lists of ShareHolderUsers?
.Select(s => s.Shareholder.ShareholderUsers

linq-to-sql SelectMany() troubles

I'm trying to find a more elegant way of pulling information from my db to my web application. Currently I pull all data in my table and use only two columns' data. It was suggested that I look into using SelectMany() to accomplish this by being able to select only the columns I need.
I'm not entirely sure how to translate the msdn example to a linq statement using a linq-to-sql db.
My current statement is this:
return db.document_library_sitefiles
.Where(item => item.SiteID == siteId)
.Select(item => item.document_library)
.GroupBy(item => item.Filename)
.Select(group => group.OrderByDescending(p=>p.Version).First())
.Where(item => !item.Filename.Contains("*")).ToList();
My current attempt, which I know is wrong, looks like this:
return db.document_library_sitefiles
.Where(item => item.SiteID == siteId)
.SelectMany(item => item.document_library, (filename, filesize)
=> new { filename, filesize })
.Select(item => new { filename = item.document_library.filename,
filesize = item.document_library.filesize })
.ToList();
Am I remotely close to getting my intended results?
Basically I want to get the data in my filename and filesize columns without pulling the rest of the data which includes file content (not my design or idea) so I'm not flooding my server with needless information just to show a simple data table of the files currently in this db.
I think you're going in the right direction. It looks like you're just changing the second query in an undesirable way. Give this a try;
return db.document_library_sitefiles
.Where(item => item.SiteID == siteId)
.Select(item => item.document_library)
.GroupBy(item => item.Filename)
.Select(group => group.OrderByDescending(p=>p.Version).First())
.Where(item => !item.Filename.Contains("*"))
.Select( item => new { filename = item.document_library.filename,
filesize = item.document_library.filesize } ).ToList();
Basically you want to keep all of the logic exactly the same as in the first query then just tack on one more select where you initialize the anonymous object to return.
In your attempt at the query you altered some of the underlying logic. You want all of the early operations to remain exactly the same (otherwise the results you return will be from a different set), you only want to transform objects in the resulting set which is why you add a select after the final where.
Cant you just append a select to you first statement?
....Where(item => !item.Filename.Contains("*"))
.Select(item => new {
item.Filename,
item.Filesize
}).ToList();

Counting grouped data with Linq to Sql

I have a database of documents in an array, each with an owner and a document type, and I'm trying to get a list of the 5 most common document types for a specific user.
var docTypes = _documentRepository.GetAll()
.Where(x => x.Owner.Id == LoggedInUser.Id)
.GroupBy(x => x.DocumentType.Id);
This returns all the documents belonging to a specific owner and grouped as I need them, I now need a way to extract the ids of the most common document types. I'm not too familiar with Linq to Sql, so any help would be great.
This would order the groups by count descending and then take the top 5 of them, you could adapt to another number or completely take out the Take() if its not needed in your case:
var mostCommon = docTypes.OrderByDescending( x => x.Count()).Take(5);
To just select the top document keys:
var mostCommonDocTypes = docTypes.OrderByDescending( x => x.Count())
.Select( x=> x.Key)
.Take(5);
You can also of course combine this with your original query by appending/chaining it, just separated for clarity in this answer.
Using the Select you can get the value from the Key of the Grouping (the Id) and then a count of each item in the grouping.
var docTypes = _documentRepository.GetAll()
.Where(x => x.Owner.Id == LoggedInUser.Id)
.GroupBy(x => x.DocumentType.Id)
.Select(groupingById=>
new
{
Id = groupingById.Key,
Count = groupingById.Count(),
})
.OrderByDescending(x => x.Count);

Categories