How to write this LINQ query as a single query? - c#

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.

Related

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

LINQ Intersect on inner collection

I have a list of Stores (of type ObservableCollection<Store>) and the Store object has a property called Features ( of type List<Feature> ). and the Feature object has a Name property (of type string).
To recap, a list of Stores that has a list of Features
I have a second collection of DesiredFeatures (of type List<string> ).
I need to use LINQ to give me results of only the stores that have all the DesiredFeatures. So far, I've only been able to come up with a query that gives me an OR result instead of AND.
Here's what that looks like:
var q = Stores.Where(s=> s.Features.Any(f=> DesiredFeatures.Contains(f.name)));
I know Intersect can help, and here's how I've used it:
var q = Stores.Where(s => s.Features.Intersect<Feature>(DesiredFeatures));
This is where I'm stuck, Intersect wants a Feature object, what I need to intersect is on the Feature.Name.
The goal is to end up with an ObservableCollection where each Store has all of the DesiredFeatures.
Thank you!
You've almost done what you need. A small refine would be to swap DesiredFeatures and s.Features.
var q = Stores.Where(s => DesiredFeatures.All(df => s.Features.Contains(df)));
It means take only those stores where desired features are all contained in features of the store.
I need to use LINQ to give me results of only the stores that have all the DesiredFeatures.
In other words, each desired feature must have a matching store feature.
I don't see how Intersect can help in this case. The direct translation of the above criteria to LINQ is like this:
var q = Stores.Where(s =>
DesiredFeatures.All(df => s.Features.Any(f => f.Name == df))
);
A more efficient way could be to use a GroupJoin for performing the match:
var q = Stores.Where(s =>
DesiredFeatures.GroupJoin(s.Features,
df => df, sf => sf.Name, (df, sf) => sf.Any()
).All(match => match)
);
or Except to check for unmatched items:
var q = Stores.Where(s =>
!DesiredFeatures.Except(s.Features.Select(sf => sf.Name)).Any()
);
Going on your intersect idea, the only way I thought of making this work was by using Select to get the Store.Features (List<Feature>) as a list of Feature Names (List<string>) and intersect that with DesiredFeatures.
Updated Answer:
var q = Stores.Where(s => s.Features.Select(f => f.Name).Intersect(DesiredFeatures).Any());
or
var q = Stores.Where(s => DesiredFeatures.Intersect(s.Features.Select(f => f.Name)).Any());
Old Answer (if DesiredFeatures is a List<Feature>):
var q = Stores.Where(s => s.Features.Select(f => f.Name).Intersect(DesiredFeatures.Select(df => df.Name)).Any());
Two things you want your code to perform.
var q = Stores.Where(s=> s.Features.All(f=> DesiredFeatures.Contains(f.name)) &&
s.Features.Count() == DesiredFeatures.Count()); // Incude Distinct in the comparison if Features list is not unique
Ensure that every Feature is DesiredFeature
Store contains all Desired features.
Code above assumes uniqueness in Features collection as well as DesiredFeatures, modify code as stated in comment line if this is not right

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.

Specific Ordering of a List of Objects using LINQ

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.

Polymorphic LINQ Query

Having a bit of a problem getting my LINQ query to return the object type I want to work with. I'm pretty close just need a little bit of input.
I have five tables, Objects, People, Locations, Collections, and CollectionEntries.
Object is the base class for People, Locations, and Collections. A Collection has many CollectionEntries which may contain entries to People, Locations, and Collections.
Given a specific collection I want to write the LINQ query to retreive the People in that collection.
So far I have this, which returns me a list of CollectionEntries ( they correspond to the People entries, yay half way! ) but I would rather have it return the instances of the People.
var people = collection.CollectionEntries.Where(
entry => entry.Object is Person ).ToList();
I have tried doing this:
var people = collection.CollectionEntries.Where(
entry => entry.Object is Person ).OfType<Person>().ToList();
but it doesn't return anything. Any suggestions of how to get a list of People from my Collection?
Try:
var people = collection.CollectionEntries.Where( entry => entry.Object is Person )
.Select(entry => (Person)entry.Object)
.ToList();
or
var people = collection.CollectionEntries.Where( entry => entry.Object is Person )
.Select(entry => entry.Object)
.Cast<Person>()
.ToList();
They should both work with your example.
Try this:-
var people = collection.CollectionEntries.Select( entry => entry.Object).OfType<Person>().ToList();
You needed to project the list to get to the .Object first and then filter according to the type.
Here is an alternative way of writing this is to use the let keyword inside query syntax
(then you can perform just one cast using the as keyword, which may be more efficient):
var people =
(from e in collection.CollectionEntries
let pers = entry.Object as Person
where pers != null select pers).ToList();

Categories