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)
Related
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));
I have a database with over 1 million records, I want to find a value in this database, but I know that this value is found somewhere in the top 1000 records.
List<string> onHoldOrderslist =
orderList.Where(m => (m.customerId == item.customerId)
&& (m.Marketplace == Market.US)
&& (m.OrderStatus == "onHold"))
.Select(s => s.OrderId)
.ToList();
In the code, I do not want to search the whole orderList database table, just the top xxx records.
My questions are:
How is it done with linq? I couldn't find any example!
Does it enhance the query performance?
Use
List<string> onHoldOrderslist = orderList.Where(m => (m.customerId == item.customerId) && (m.Marketplace == Market.US) && (m.OrderStatus == "onHold"))
.OrderBy(x => x.WhateverMakesSense)
.Take(1000)
.Select(s => s.OrderId)
.ToList();
Please note that ordering is important as otherwise you may get random 1000 elements...
Given you say this is "a record" and you are only returning one, don't worry about the fact that it's in the top 1000 (and top doesn't even mean anything unless you specify an order). Using Take(1000) after the where clause will do nothing as there is only one record anyway. All you need is an index, in this case on customerId, Marketplace, and OrderStatus.
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.
I have this List:
string[] countries = {
"USA",
"CANADA"
};
When I run this query :
query = (from user in db where
user.Orders.Any(order => order.Price > 10 &&
countries.Contains(order.DestinationCountry)))
Output is a list of users that have Orders sent to "USA" OR "Canada".
but I want the list of users that have Orders sent to both "USA" AND" "CANADA".
I can do this using below code but i'm searching for a pure linq solution without any ForEach:
foreach (country in countries) {
query = (from user in query where
user.Orders.Any(order => order.Price > 10 &&
order.DestinationCountry == country));
}
Answers:
A. Using .Aggregate()
Generated query is just like For Each.
B.where countries.All(c => user.Orders.Any(o => o.Price > 10 && o.DestinationCountry == c))
When there is no element in Countries List (When I want all users based only on Price parameter), the result is not correct and other parameter is not considered!
Update 1:
I have tried .All() instead of .Contains() before posting and it returns 0 users.
Update 2:
I have updated my question to make it closer to the real problem.
lets say Country is not the only parameter.
Update 3:
Checked some answers and added the result to my question.
So you want a list of the users such that all the countries in the list are present in the set of order destinations?
Logically, that would be:
query = from user in db
where countries.All(c => user.Orders.Any(o => o.DestinationCountry == c))
select ...;
However, I'm not confident that EF will do what you want with that. It's not clear to me what the right SQL query would be to start with - in a simple way, at least.
query =
db.Users.Where(user =>
countries.All(country =>
user.Orders.Any(order =>
order.DestinationCountry == country)))
You can do it like this:
query = (from user in db where
user.Orders
.Where(o => countries.Contains(o.DestinationCountry))
.GroupBy(o => o.DestinationCountry)
.Count() == countries.Count
);
The idea is to keep only the orders going to countries of interest, then group by country, and check that the number of groups equals the number of countries.
It's possible using Enumerable.Aggregate:
query = countries.Aggregate(query,
(q, c) =>
from user in q
where user.Orders.Any(order => order.DestinationCountry == c)
select user);
but really, this is harder to understand than your foreach loop, so I'd just go with that.
Note that although I refer to a member of Enumerable, that member of Enumerable is actually building up an IQueryable<User> query chain just like your foreach loop, so this will not cause the filtering to move to the client.
I am new one with Linq and I would like to modify my old c# code to use Linq.
The idea of this code to select all tables where it's not set and reference’s field PrimaryTable equal "myTable"
foreach (Table table in dbServer.Tables)
{
if (!table.IsSet)
{
foreach (Reference refer in table.References)
{
if (refer.PrimaryTable == "myTable")
{
tables.Add(table);
}
}
}
}
After digging in internet I have got this code
var q = from table in dbServer.Tables
let refers = from refer in table.References
where refer.PrimaryTable == "myTable"
select refer.ForeignTable
where refers.Contains(table.Name)
select table;
But it does not work at all and I need your help to make it works.
Thanks in advance.
var tables = dbServer.Tables
.Where(t => !t.IsSet)
.SelectMany(t => t.References)
.Where(r => r.PrimaryTable == "myTable")
.ToList();
Assuming tables is a List<T>
EDIT: As the comment points out, this isn't the same as the original - it looks like what you actually want is this:
var tables = dbServer.Tables
.Where(t => !t.IsSet && t.References.Any(r => r.PrimaryTable == "myTable"))
.ToList();
This will give you all the tables which have a reference whose PrimaryTable is 'myTable' which assumes that there will only be one matching reference table. Otherwise you could have the same table added multiple times.
Just need to use two from's
var q = from table in dbServer.Tables
where !table.IsSet
from refer in table.References
where refer.PrimaryTable == "myTable"
select table;
EDIT
Actually, I'm a bit confused by this code. Are you sure it's doing what it's meant to do? In particular, what's throwing me off is the fact that you're enumerating over table.References, but then, when a certain condition holds for a particular Reference (i.e., refer.PrimaryTable == "myTable"), you're adding the Table (table) instead of the Reference (refer).
What this means is that if a Table has multiple Reference objects with PrimaryTable == "myTable", your tables collection might contain multiple copies of this Table. Is this correct?
I'm going to go out on a limb and guess that what you really want to check is simply that a Table has, in its References collection, any Reference object with PrimaryTable == "myTable". If that's the case, in your original code after tables.Add(table) I would have simply added break to avoid duplicates. (It may be that only one Reference in each collection would ever have the same PrimaryTable, which case you'd be fine; but you could still stop enumerating at this point. Unless of course you want the duplicates.)
In any case, Lee's code (and what I have below) is not duplicating this behavior. Rather, it's adding the Reference objects to a list (because the final ToList call is on an IEnumerable<Reference>).
It seems like, if what I've described above is the behavior you're after, you might want to do this:
var tables = dbServer.Tables
.Where(table => !table.IsSet)
.Where(
table => table.References.Any(refer => refer.PrimaryTable == "myTable")
).ToList();
ORIGINAL ANSWER
I'm going to expand on Lee's answer. Let's analyze it line by line.
// 1. enumerating over a collection
foreach (Table table in dbServer.Tables)
{
// 2. checking a condition
if (!table.IsSet)
{
// 3. enumerating over another collection
foreach (Reference refer in table.References)
{
// 4. checking a condition
if (refer.PrimaryTable == "myTable")
{
// 5. adding to a collection
tables.Add(table);
}
}
}
}
OK. So we've got:
Enumeration - simple -- that's where we start
Condition checking - we'll need a Where
Enumeration over another collection - SelectMany
Condition checking - Where again
Adding - most likely ToList (depends on what type of collection you want)
Here's what it comes out to:
var tables = dbServer.Tables // step 1
.Where(table => !table.IsSet) // step 2
.SelectMany(table => table.References) // step 3
.Where(refer => refer.PrimaryTable == "myTable") // step 4
.ToList(); // step 5
Make sense?
tables.AddRange(dbServer.Tables
.Where(t => !t.IsSet)
.SelectMany(t => table.References)
.Where(r => r.PrimaryTable == "myTable"));