Specific Ordering of a List of Objects using LINQ - c#

I am using LINQ and I want to order a list using one of the columns in my DB table. The issue is that I want to order a list of people by their branch which I am able to do but I want to place the branch in a certain order.
Right now I am using this:
phoneList.OrderBy(e => e.Branch).ThenBy(e => e.FullName)
Say that i have these branches avaialble: 82pk,corp,prfe,hrbd.
My current code will sort the people in this branch order: 82pk,corp,hrbd,prfe
I want to sort the people in this branch order: corp,82pk,prfe,hrbd
How can I use LINQ to order my list manually given my specific order?
This is what i am trying to accomplish:
phoneList.OrderBy(e => e.Branch == corp).ThenBy(e => e.Branch == 82pk).ThenBy(e => e.Branch == prfe).ThenBy(e => e.Branch == hrbd)

Start with an array of your required order
var branchOrder = new[]{"corp","82pk","prfe","hrbd"};
Then order by the index position in this list:
phoneList.OrderBy(e => branchOrder.IndexOf(e.Branch)).ThenBy(e => e.FullName);
This has the added benefit that it works as expected even with EF queries.
This will get slower as the number of items grows, as described in comments there is a simple enhancement to store the "branch" and required order in a dictionary.
var branchOrder = new Dictionary<string,int>(){
{"corp",1},
{"82pk",2},
... etc
}
phoneList.OrderBy(
e => branchOrder.ContainsKey(e.Branch)
? branchOrder[e.Branch]
: 0) // give a default to protect against invalid key
.ThenBy(e => e.FullName);

Let your Branch-class implement ICompareble with the logic you described. Then Linq's OrderBy will sort as you want.

Related

Using LINQ to filter records in an object

I have a deserialized Json object that I am trying to filter before processing. The data looks like this...
Company Division LastModDate Lot's of other columns/objects
123 1 7/1/2021
123 1 8/1/2022
123 2 8/1/2022
How can I get all the information in the original object and get rid of records that are not the latest for each Company/Division group?
I tried this...
var filtered = origObject.GroupBy(g=> new {g.Company,g.Division})
I don't know where to go next.
If I were doing this in SQL then I would be using row_number and just taking the 1 for example.
You could try something like
var filtered = origObject
.GroupBy(x => new {g.Company,g.Division})
.Select(g => g.OrderByDescending(x => x.LastModDate).First());
This will select one latest object from each group.
Edit: I'm not sure without a compiler at hand if this will group correctly - your grouping key is an anonymous object, I don't remember if they have any equality comparer other than by reference. You could try using a record instead, records have equality by value of all their properties - .GroupBy(g => (g.Company,g.Division)). Or just group by a string key such as $"{g.Company},{g.Division}",
A much more efficient way of doing this is as follows:
var filtered = origObject
.OrderByDescending(w => w.LastModDate)
.DistinctBy(w => (w.Company, w.Division));
This avoids the heavy array allocation and copying of GroupBy, especially since you only care about one item from its result.

Adding items to the list inside foreach loop

epublic ActionResult ExistingPolicies()
{
if (Session["UserId"]==null)
{
return RedirectToAction("Login");
}
using(PMSDBContext dbo=new PMSDBContext())
{
List<Policy> viewpolicy = new List<Policy>();
var userid = Session["UserId"];
List<AddPolicy> policy= dbo.AddPolicies.Where(c => c.MobileNumber ==
(string)userid).ToList();
foreach(AddPolicy p in policy)
{
viewpolicy=dbo.Policies.Where(c => c.PolicyId ==p.PolicyId).ToList();
}
Session["Count"] = policy.Count;
return View(viewpolicy);
}
}
Here the policy list clearly has 2 items.But when I iterate through foreach,the viewpolicy list only takes the last item as its value.If break is used,it takes only the first item.How to store both items in viewpolicy list??
Regards
Surya.
You can iterate through policies and add them by one to list with Add, but I would say that often (not always, though) better option would be to just retrieve the whole list from DB in one query. Without knowing your entities you can do at least something like that:
List<AddPolicy> policy = ...
viewpolicy = dbo.Policies
.Where(c => policy.Select(p => p.PolicyId).Contains(c.PolicyId))
.ToList();
But if you have correctly set up entities relations, you should be able to do something like this:
var viewpolicy = dbo.AddPolicies
.Where(c => c.MobileNumber == (string)userid)
.Select(p => p.Policy) //guessing name here, also can be .SelectMany(p => p.Policy)
.ToList();
Of course; instead of adding to the list, you replace it with a whole new one on each pass of the loop:
viewpolicy=dbo.Policies.Where(c => c.PolicyId ==p.PolicyId).ToList()
This code above will search all the policies for the policy with that ID, turn it into a new List and assign to the viewpolicy variable. You never actually add anything to a list with this way, you just make new lists all the time and overwrite the old one with the latest list
Perhaps you need something like this:
viewpolicy.Add(dbo.Policies.Single(c => c.PolicyId ==p.PolicyId));
This has a list, finds one policy by its ID number (for which there should be only one policy, right? It's an ID so I figured it's unique..) and adds it to the list
You could use a Where and skip the loop entirely if you wanted:
viewpolicy=dbo.Policies.Where(c => policy.Any(p => c.PolicyId == p.PolicyId)).ToList();
Do not do this in a loop, it doesn't need it. It works by asking LINQ to do the looping for you. It should be converted to an IN query and run by the DB, so generally more performant than dragging the policies out one by one (via id). If the ORM didn't understand how to make it into SQL you can simplify things for it by extracting the ids to an int collection:
viewpolicy=dbo.Policies.Where(c => policy.Select(p => p.PolicyId).Any(id => c.PolicyId == id)).ToList();
Final point, I recommend you name your "collections of things" with a plural. You have a List<Policy> viewpolicy - this is a list that contains multiple policies so really we should call it viewPolicies. Same for the list of AddPolicy. It makes code read more nicely if things that are collections/lists/arrays are named in the plural
Something like:
viewpolicy.AddRange(dbo.Policies.Where(c => c.PolicyId ==p.PolicyId));

Select 1 column from a Group By LINQ query

I think what I need is relatively simple but every example I Google just returns results using First(), which I'm already doing. Here is my expression:
var options = configData.AsEnumerable().GroupBy(row => row["myColumn"]).Select(grp => grp.First());
What I need is only ONE column from the grp portion and to be able to suffix .ToList() on there without an error. As it stands I receive 4 columns, but only need a specific one, kind of like if this (grp => grp["myColumn"]), didn't result in error the Error 153 Cannot apply indexing with [] to an expression of type 'System.Linq.IGrouping<object,System.Data.DataRow>'
Also, Key does not work in the grouping portion as these results are from a DataTable object. See here - >
If you want only the keys, you can use
var options = configData.AsEnumerable().Select(row=>row["myColumn"]).Distinct();
I think that this is what you want:
configData.AsEnumerable()
.GroupBy(r => r["myColumn"])
.Select(g => new
{
myColumnValue = g.Key,
myColumnItems = g.Select(r => r["OtherColumn"]).ToList()
});
Do you understand how/what this does though? Try it out and inspect the resulting IEnumerable. I'm not sure you have a perfect understanding on how GroupBy works but take your time with above example.
See this part:
new
{
myColumnValue = g.Key,
myColumnItems = g.Select(r => r["OtherColumn"]).ToList()
}
This creates an anonymous type which outputs the values of "OtherColumn" column into a list grouped by "myColumn" where value of "myColumn" is in the myColumnValue property.
I'm not sure this answers your question but it looks like this is what you want.
The variable g is of the type IGrouping<object, DataRow>, it's not DataRow. The IGrouping interface is designed to provide a list of DataRow's grouped by object values - it does not produce a flat list, if it did then it would just be a Sort, not GroupBy.
Just specify the field you want after your call to First() e.g.
.Select(grp => grp.FirstOrDefault()["MyFieldName"]);
This will take the first record from the grouping and select the specified field from that record.

Using Linq Grouping how to I order by specific Group then remaining Groups descending

This Code is working example of the content of a select case statement. It is in response to a link button click who's ID is passed in via Session variable. The link buttons represent High(3) Medium(2) and Low(1) Risk categories.
The logic here is; if you select Medium(2) it's related rows (riskCategory =2) are displayed first then the remaining rows ( risk category ) are listed descending so 3 then 2 then 0)
As I said my ugly implementation of the Linq Concat function to accomplish my goal does produce correct results, but it also showcases my need to spend more weekends reviewing and creating better intricate samples than the simple 101 Link Samples tutorial project provides.
There must be a more elegant way to group and order by while allowing for the groups to be ordered representing the selected group first, with remaining groups descending. Again Select Group 1 LowRiskCategory, I'll have to display LowRiskCategory first (1) then 3, 2, & 0 respectively in the sorted results set.
var midQuery = enumerableVersionTable.Where(x => x["RiskCategory"].Equals(intRiskCategoryEnum));
midQuery.OrderByDescending(v => v["DateOfService"]);
midQuery.OrderBy(v => v["Reviewed"]);
var midQueryZero = enumerableVersionTable.Where(x => x["RiskCategory"].Equals(0));
midQueryZero.OrderByDescending(v => v["DateOfService"]);
midQueryZero.OrderBy(v => v["Reviewed"]);
var midQueryOne = enumerableVersionTable.Where(x => x["RiskCategory"].Equals(1));
midQueryOne.OrderByDescending(v => v["DateOfService"]);
midQueryOne.OrderBy(v => v["Reviewed"]);
var midQueryThree = enumerableVersionTable.Where(x => x["RiskCategory"].Equals(3));
midQueryThree.OrderByDescending(v => v["DateOfService"]);
midQueryThree.OrderBy(v => v["Reviewed"]);
var querySummation = midQuery.Concat(midQueryThree);
querySummation = querySummation.Concat(midQueryOne);
querySummation = querySummation.Concat(midQueryZero);
dtQueryResults = querySummation.CopyToDataTable()
Just the sight of those hardcoded numeral values after the translated enum value for case 2:
makes me wana hurl. Theres gotta be more elegant way to do the groups. Order by a specific group. and of course apply all my other odd sorting, as you see date of service and reviewed.
Lastly if you going to AGAIN vote down
at least explain why please thank you
var dtQueryResults = yourData
.OrderByDescending(v => v["RiskCategory"] == intRiskCategoryEnum)//true for ==2 goes first, false goes then
.ThenBy(v => v["RiskCategory"]) //the rest is sorted normally
.ThenBy(v => v["Reviewed"]) //inside the groups, the rest of your sorts is used
.ThenByDescending(v => v["DateOfService"]);
Just change the lambda which you have used for the OrderBy. you are not limited to picking up just one field. I'd use a tertiary expression to select what to sort on based on what is selected.

How to write this LINQ query as a single query?

Consider this code:
var query = from groupRole in CurrentItem.MEMGroupRoles
select groupRole.MEMRole;
this.AvailableRoles = this.allRoles.Except(query.AsEnumerable()).ToList();
In this code I take allRoles except those roles that CurrentItem already have. 2 issues:
It doesn't work because I compare on objects and those objects are different instances
I don't like 2 lines and like to improve.
Here is pseudo-code on what I really need to do right now:
var queryIds = from groupRole in CurrentItem.MEMGroupRoles
select groupRole.MEMRole.RoleId;
this.AvailableRoles = this.allRoles.Except(where RoleId query.AsEnumerable()).ToList();
How do I write query like this?
EDIT:
explanation:
allRoles contains list of MEMRole objects
CurrentItem.MEMGroupRoles contains list of MEMGroupRole objects and each MEMGroupRole contains MEMRole inside
I want to SELECT all MEMRole objects that's inside allRoles EXCEPT those MEMRoles that burries inside CurrentItem. First code snippet would work, but I need to compare MEMRole to MEMRole by MEMRole.RoleId since it's a different instances of the same database entity.
You could override Equals() and GetHashCode() if the role object is such that it would make sense to identify it with role id. If that is not the case, you could create a role comparer class that implements IEqualityComparer<>. Except() takes equality comparer as second parameter.
Here is a solution that creates a lookup for role ids and uses it to filter the roles. However, I do think that the alternatives above are better solutions for your problem.
var lookup = CurrentItem.MEMGroupRoles
.ToLookup(groupRole => groupRole.MEMRole.RoleId);
this.AvailableRoles = this.allRoles
.Where(role => !lookup.Contains(role.RoleId))
.ToList();
Following the approach you suggested:
var ids = CurrentItem.MEMGroupRoles.Select(g => g.MMERole.RoleId);
this.AvailableRoles = this.allRoles.Where(r => ids.All(i => i != r.RoleId));
Alternatively (althought I wouldn't go that road), if you must have single query, you can append both roles collections (current and all), group them by RoleId and pick groups that only have single member:
this.AvailableRoles = CurrentItem.MEMGroupRoles
.Select(g => g.MEMRole)
.Concat(this.allRoles)
.GroupBy(r => r.RoleId)
.Where(g => g.Count() == 1)
.Select(g => g.First());
This results in roles that weren't in CurrentItem.MEMGroupRoles collection. But once again, it's just ... for sport :)
Is this LINQ to SQL?
If so, use DataContext.Log property to see the actual SQL that is being passed to the database, which may help you diagnose the problem.

Categories