Linq nested loop search one specific top level element , collection within collections - c#

I want to get the company whose employees id card issued with the specific number, sort of finding the exact element inside nested collection.
Using first or default 3 times does not seems to be a correct way.
> var company = cprIdentificationReply.Companies
> .FirstOrDefault(x => (x.Employee
> .FirstOrDefault(y => (y.IDCardIssued
> .FirstOrDefault(z => z.CardNumber
> .Equals(number,StringComparison.InvariantCultureIgnoreCase))) != null)
> != null));
What can be a proper way of achieving the same?

You may want to use the Any extension method:
var companies = cprIdentificationReply.Companies
.Where(x => (x.Employee
.Any(y => (y.IDCardIssued
.Any(z => z.CardNumber
.Equals(number, StringComparison.InvariantCultureIgnoreCase)
)
)
).ToList();

If you want better looking code why don't use LINQ query syntax.
I always find it easier to read LINQ query when looking at someone else's code, especially for complex operations.
Something like this:
var company = (from company in cprIdentificationReply.Companies
from empl in company.Employee
from idCardIss in empl .IDCardIssued
where idCardIss.CardNumber.Equals(number, StringComparison.InvariantCultureIgnoreCase)
select c).FirstOrDefault();

Related

Select parent but order by certain child with dynamic linq

I'am trying to write following in Dynamic-linq. I have following statement in ordinary linq
var result = DBContext.Report
.Include(h => h.ReportRoleMemberships)
.Join(DBContext.ReportRoleMemberships.Where(Rrm =>Rrm.ValidTo==null && Rrm.UserId==userId),
t=>t.Id,
y=>y.Report_Id,
(t,y) => new { Ha=t, Rrm=y })
.OrderBy(h => h.Rrm.ReportRoleValue);
userId is a int with the UserId in the code above.
I want to sort on ReportRoleValue and feels this feel a little bit over the top, but I havent a clue how I should write this is dynamic linq , since it is orderby on one-to-many parent-child relationship.
Assuming that there is a navigation property from ReportRoleMemberships to Reports, you can turn the query around and by that remove the separate Join, e.g.:
var result = DBContext.ReportRoleMemberships
.Include(h => h.Report)
.Where(Rrm => Rrm.ValidTo == null && Rrm.UserId == userId)
.OrderBy(h => h.ReportRoleValue);
This query selects the entries from ReportRoleMemberships and applies the conditions to them and also populates the Report part so that you retrieve both the ReportRoleMembership and Report information in one query.

How to filter collection in linq based on some subobjects in that list

I want to get only those employees that have at least one service, and which that service is younger than current date (dt)
I tried with .Any() but it returns me all employees with all services (it doesnt check that date)
var employees =
employeeService.GetAllActiveEmployeesForCompanyForLocation(companyId, location.Id)
.Where(x => x.IsCounter && x.Services != null && x.Services.Count > 0 &&
x.Services.Any(u => u.ActiveTo >= dt.Value));
I want to filter just those employees which have at least one service or more where ActiveTo is not in the past (dt is a current datetime.now)
You can chain multiple .Where and .Select statements after one another. Your LINQ query is very hard to read without more specific information about your objects.
To make it more readable, I would suggest splitting your requirements into separate queries, like so:
var employeesWithActiveServices =
employeeService.GetAllActiveEmployeesForCompanyForLocation(companyId, location.Id)
.Where(e => e.IsCounter && e.Services.Count >= 1)
.Select(e => e.Services.Contains(s => s.ActiveTo >= DateTime.Now)).ToList();
Notice how I removed your e.Services != null check. It is redundant when you're already checking e.Services.Count.
This was made quickly of the top of my head, so you may need to tweak it to suit your needs.
It is still a hard LINQ query to read without seeing the objects it is querying, but this at least makes the query itself more readable.
Try to remove any additional null, Count checks, otherwise SQL will be complex and slow:
var employees =
employeeService.GetAllActiveEmployeesForCompanyForLocation(companyId, location.Id)
.Where(x => x.IsCounter &&
x.Services.Any(u => u.ActiveTo >= dt.Value));
EF Core translates your query into the SQL, which do not have NullReference exception.

Entity Framework (using In and Select Distinct)

I am relatively new to Entity Framework 6.0 and I have come across a situation where I want to execute a query in my C# app that would be similar to this SQL Query:
select * from periods where id in (select distinct periodid from ratedetails where rateid = 3)
Is it actually possible to execute a query like this in EF or would I need to break it into smaller steps?
Assuming that you have in your Context class:
DbSet<Period> Periods...
DbSet<RateDetail> RateDetails...
You could use some Linq like this:
var distincts = dbContext.RateDetails
.Where(i => i.rateId == 3)
.Select(i => i.PeriodId)
.Distinct();
var result = dbContext.Periods
.Where(i => i.Id)
.Any(j => distincts.Contains(j.Id));
Edit: Depending on your entities, you will probably need a custom Comparer for Distinct(). You can find a tutorial here, and also here
or use some more Linq magic to split the results.
Yes, this can be done but you should really provide a better example for your query. You are already providing a bad starting point there. Lets use this one:
SELECT value1, value2, commonValue
FROM table1
WHERE EXISTS (
SELECT 1
FROM table2
WHERE table1.commonValue = table2.commonValue
// include some more filters here on table2
)
First, its almost always better to use EXISTS instead of IN.
Now to turn this into a Lambda would be something like this, again you provided no objects or object graph so I will just make something up.
DbContext myContext = this.getContext();
var myResults = myContext.DbSet<Type1>().Where(x => myContext.DbSet<Type2>().Any(y => y.commonValue == x.commonValue)).Select(x => x);
EDIT - updated after you provided the new sql statement
Using your example objects this would produce the best result. Again, this is more efficient than a Contains which translates to an IN clause.
Sql you really want:
SELECT *
FROM periods
WHERE EXISTS (SELECT 1 FROM ratedetails WHERE rateid = 3 AND periods.id = ratedetails.periodid)
The Lamda statement you are after
DbContext myContext = this.getContext();
var myResults = myContext.DbSet<Periods>()
.Where(x => myContext.DbSet<RateDetails>().Any(y => y.periodid == x.id && y.rateid == 3))
.Select(x => x);
Here is a good starting point for learning about lamda's and how to use them.
Lambda Expressions (C# Programming Guide).
this is your second where clause in your query
var priodidList=ratedetails.where(x=>x.rateid ==3).DistinctBy(x=>x.rateid);
now for first part of query
var selected = periods.Where(p => p.id
.Any(a => priodidList.Contains(a.periodid ))
.ToList();

LINQ: Generate "AND" Expression instead of "OR" when using "CONTAINS"

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.

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)

Categories