Adding items to the list inside foreach loop - c#

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

Related

ASP.NET MVC C# Select and Where Statements

I'm having trouble understanding .Select and .Where statements. What I want to do is select a specific column with "where" criteria based on another column.
For example, what I have is this:
var engineers = db.engineers;
var managers = db.ManagersToEngineers;
List<ManagerToEngineer> matchedManager = null;
Engineer matchedEngineer = null;
if (this.User.Identity.IsAuthenticated)
{
var userEmail = this.User.Identity.Name;
matchedEngineer = engineers.Where(x => x.email == userEmail).FirstOrDefault();
matchedManager = managers.Select(x => x.ManagerId).Where(x => x.EngineerId == matchedEngineer.PersonId).ToList();
}
if (matchedEngineer != null)
{
ViewBag.EngineerId = new SelectList(new List<Engineer> { matchedEngineer }, "PersonId", "FullName");
ViewBag.ManagerId = new SelectList(matchedManager, "PersonId", "FullName");
}
What I'm trying to do above is select from a table that matches Managers to Engineers and select a list of managers based on the engineer's id. This isn't working and when I go like:
matchedManager = managers.Where(x => x.EngineerId == matchedEngineer.PersonId).ToList();
I don't get any errors but I'm not selecting the right column. In fact the moment I'm not sure what I'm selecting. Plus I get the error:
Non-static method requires a target.
if you want to to select the manager, then you need to use FirstOrDefault() as you used one line above, but if it is expected to have multiple managers returned, then you will need List<Manager>, try like:
Update:
so matchedManager is already List<T>, in the case it should be like:
matchedManager = managers.Where(x => x.EngineerId == matchedEngineer.PersonId).ToList();
when you put Select(x=>x.ManagerId) after the Where() now it will return Collection of int not Collection of that type, and as Where() is self descriptive, it filters the collection as in sql, and Select() projects the collection on the column you specify:
List<int> managerIds = managers.Where(x => x.EngineerId == matchedEngineer.PersonId)
.Select(x=>x.ManagerId).ToList();
The easiest way to remember what the methods do is to remember that this is being translated to SQL.
A .Where() method will filter the rows returned.
A .Select() method will filter the columns returned.
However, there are a few ways to do that with the way you should have your objects set up.
First, you could get the Engineer, and access its Managers:
var engineer = context.Engineers.Find(engineerId);
return engineer.Managers;
However, that will first pull the Engineer out of the database, and then go back for all of the Managers. The other way would be to go directly through the Managers.
return context.Managers.Where(manager => manager.EngineerId == engineerId).ToList();
Although, by the look of the code in your question, you may have a cross-reference table (many to many relationship) between Managers and Engineers. In that case, my second example probably wouldn't work. In that case, I would use the first example.
You want to filter data by matching person Id and then selecting manager Id, you need to do following:
matchedManager = managers.Where(x => x.EngineerId == matchedEngineer.PersonId).Select(x => x.ManagerId).ToList();
In your case, you are selecting the ManagerId first and so you have list of ints, instead of managers from which you can filter data
Update:
You also need to check matchedEngineer is not null before retrieving the associated manager. This might be cause of your error
You use "Select" lambda expression to get the field you want, you use "where" to filter results

Linq Remove results with ALL statement

Hi I'm trying to use Linq to remove "all" entities from a list.
Problem: I'm searching for users that have certain certificates in my database. Thing is that it returns them row by row.... But what I need to check is: If the user holds all the required certificates. This should be checked against my int array.
This is my array: [3,5,16], now I want to delete all user who does not have all three of those from the list. Name of the array in code is mandatory!
The listitems I get back looks like this
listitem.CertificateValue
listitem.Uid
listitem.NameOfPerson
So basicly for this example Peter has three rows in the list, in this case all the rows needed to stay in the list. But Philip only has 2 rows and hence both of these should be deleted since he does not fullfill the total search criteria.
Also copyOfMandatoryis just to not mess with the original collection and cause an expection(collection size changed).
foreach (var item in copyOfMandatory)
{
if (!mandatoryusers.All(i => mandatory.Contains(i.CertificateValue)
|| i.Uid == item.Uid))
{
mandatoryusers.RemoveAll(i => i.Uid == item.Uid);
}
}
UPDATE
RemoveAll works like a charm it the if statement that does not work as expected.
Doing this it does not take away any part of the list, I began wiht && instead of || but whne doing that it kills everything but the last person it encounters as long as he/she fullfills the search criteria.
Anyone have a hint on how to do this?
I would try something like that
var uIdToRemove = mandatoryusers.GroupBy(m => m.Uid)
.Where(g => mandatory.Except(g.Select(s => s.CertificateValue)).Any())
.Select(g => g.Key).ToList();
mandatoryusers.RemoveAll(x => uidToRemove.Contains(x.Uid));
Your All call is not granular enough: it is trying to ensure that ALL entries exist at all times... Not that all entries PER USER exist.
Try converting each entry to a dictionary:
var dict = new Dictionary<int, List<ItemType>>();
foreach (var mandatoryItem in mandatoryItems)
{
List<ItemType> itemTypeValue = null;
if (!dict.TryGetValue(mandatoryItem.Uid, out itemTypeValue)
{
itemTypeValue = new List<ItemType>();
dict.Add(mandatoryItem.Uid, itemTypeValue);
}
itemTypeValue.Add(mandatoryItem);
}
Now you have all ItemType at the key of Uid. From here, use LINQ:
mandatoryusers = mandatoryusers.Where(i => dict[i.Uid].All(x => mandatory.Contains(x.CertificateValue));
Your if All criteria is off.
if (!mandatoryusers.All(i => mandatory.Contains(i.CertificateValue)
|| i.Uid == item.Uid))
{
mandatoryusers.RemoveAll(i => i.Uid == item.Uid);
}
It needs to be with an && not an || and you should call Any() instead of All()
if (!mandatoryusers.Any(i => mandatory.Contains(i.CertificateValue)
&& i.Uid == item.Uid))
{
mandatoryusers.RemoveAll(i => i.Uid == item.Uid);
}
Hopefully I understood what your logic and question correctly.
Your if statement isn't correct (as you stated) - it's attempting to check whether all items contain a certificate with an id in mandatory or where the userid is the current item. What you should be doing is filtering by userid first and then checking the certificates.
This isn't the way I would do it, though. I'd group the results by User and then check the certificates
var usersWithAllCertificates = mandatoryUsers.GroupBy(mu => mu.Uid)
//Select the ones that have all 3 certificates
.Where(g => g.Select(u => u.CertificateValue)
.Intersect(mandatory).Count() == 3)
.Select(g => g.ToList());
The Intersect operator will combine the lists and the result will be the items that are the same in both lists. So, if the user has all 3 certificates (3, 5 and 16) the result of the intersect will be 3 items. The usersWithAllCertificates object will include all the users you want. This is explicitely selecting the values you want instead of removing the ones you don't want, which imo is a better way of going about it. Note that this assumes each user is only in the list once (i.e. only has 3 certificates)

In LINQ, how can I do an .OrderBy() on data that came from my .Include()?

Here's what I'm doing:
List<Category> categories =
db.Categories.Include("SubCategories").OrderBy(c => c.Order).ToList();
I have a column on my categories table called "Order" which simply holds an integer that gives the table some kind of sorting order.
I have the same column on my "SubCategories" table...
I want to know the simplest solution to add the sort on my subcategories table... something like:
List<Category> categories =
db.Categories.Include("SubCategories").OrderBy(c => c.Order)
.ThenBy(c => c.SubCategories as x => x.Order).ToList();
I'd like to keep it in this type of LINQ format... (method format)...
Keep in mind, i'm working in MVC and need to return it to a view as a model. I've been having trouble with errors because of AnonymousTypes...
I'm not sure if this is supported, but here's how it might be done:
List<Category> categories =
db.Categories.Include(c => c.SubCategories.OrderBy(s => s.Order)).OrderBy(c => c.Order)
The Include method now supports Expressions like this, but I'm not certain if it supports ordering too.
You might be better off sorting the subcategories when you use them, probably in your view.
For example:
#for (var cat in Model.Categories) {
#cat.Name
#for (var sub in cat.SubCategories.OrderBy(c => c.Order) {
#sub.Name
}
}
You can split the single query into 2 queries which just fill up the context:
IQueryable<Category> categoryQuery = db.Categories.Where(c=> /*if needed*/);
List<Category> categories = categoryQuery.OrderBy(c => c.Order).ToList();
categoryQuery.SelectMany(c => c.SubCategories)
.OrderBy(sub => sub.Order)
.AsEnumerable().Count(); // will just iterate (and add to context) all results
You even don't need the error prone string "SubCategories" anymore then.
If Category.SubCategories is a collection in itself, then you won't be able to order using the existing extension methods (and c => c.SubCategories as x => x.Order translates to almost nothing, basically saying that SubCategories is a Func<SubCategory, bool>)
If you're content to have the sorting done in memory (which shouldn't really be a problem since you're already fetching them from the database anyway, provided you don't have thousands of the things) you can implement your own custom IComparer<Category> which interrogates the SubCategories of each Category to determine whether one Category should be placed above or below another Category in a sort operation.
Your statement would then be:
var categories = db.Categories.Include("SubCategories").OrderBy(x => x, new CategorySubCategoryComparer())

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.

Get a distinct list

I want to select a distinct list.
The following code is not working:
public IQueryable<BusinessObjects.Order> GetByBusinessId(Guid Id)
{
rentalEntities db = DataContextFactory.CreateContext();
List<Rental.BusinessObjects.Order> transformedList = new List<BusinessObjects.Order>();
foreach (Rental.DataObjects.EntityModel.Order item in db.Orders.Where(x => x.BusinessID == BusinessId).ToList())
{
transformedList.Add(OrderMappers.ToBusinessObject(item));
}
return( transformedList.AsQueryable()).Distinct();
}
Try this:
return Rental.DataObjects.EntityModel.Order item in db.Orders
.Where(x => x.BusinessID == BusinessId)
.Distinct()
.Select(item => OrderMappers.ToBusinessObject(item));
This should move the distinct operation to the underlying database call as it's applied before the query is materialized - this is more efficient as the duplicate rows aren't retrieved from the database server. If for some reason you don't want to do that, then check your equals implementation (as mentioned by Sorin)
You may want to check how your business objects implement Equals(), my guess is they are are different even if they have (let's say) the same ID.
You might like to try the DistinctBy() extension method from the MoreLinq library. This lets you easily control the exact semantics of how two objects are compared for distinctness. For instance:
return transformedList.AsQueryable().DistinctBy(orderBO => orderBO.OrderId);
http://morelinq.googlecode.com/files/morelinq-1.0-beta.zip

Categories