Count child matches in linq Entity Framework - c#

I try to count the number of child elements for each parent where the parent id is in a list if ints. But the count is not correct. Is it not possible to make a query like this? What is the correct way of doing this?
List<int> ids = [1, 2, 3];
var counts = (from d in db.Parent where ids.Contains(d.Id) select d.Child.Count()).ToList();

Assuming Child is a collection, because you are attempting to count it.
Try the following:
var counts = db.Parent.Where(x => ids.Contains(x.Id)).SelectMany(x => x.Child).Count();
"Count" will work on an IEnumerable. However it seems you are trying to work with a collection of IEnumerables. In these situations use "SelectMany" so that all the resulting collections are joined into one single collection.

The query does exact what I want, sorry for that. My problem was that the ids list was not sorted and that somehow made the query to not sort the output as the ids list was sorted. By sort the ids list (ids.Orderby(id)) this question was solved.

A list of counts and nothing else may offer sufficient information, but often some information on the containing entity will be useful (if only to prevent confusion):
List<int> ids = [1, 2, 3];
var counts = (from d in db.Parent where ids.Contains(d.Id)
select new
{
Parent = d.Name,
ChildCount = d.Child.Count()
}).ToList();

Related

Why is linq reversing order in group by

I have a linq query which seems to be reversing one column of several in some rows of an earlier query:
var dataSet = from fb in ds.Feedback_Answers
where fb.Feedback_Questions.Feedback_Questionnaires.QuestionnaireID == criteriaType
&& fb.UpdatedDate >= dateFeedbackFrom && fb.UpdatedDate <= dateFeedbackTo
select new
{
fb.Feedback_Questions.Feedback_Questionnaires.QuestionnaireID,
fb.QuestionID,
fb.Feedback_Questions.Text,
fb.Answer,
fb.UpdatedBy
};
Gets the first dataset and is confirmed working.
This is then grouped like this:
var groupedSet = from row in dataSet
group row by row.UpdatedBy
into grp
select new
{
Survey = grp.Key,
QuestionID = grp.Select(i => i.QuestionID),
Question = grp.Select(q => q.Text),
Answer = grp.Select(a => a.Answer)
};
While grouping, the resulting returnset (of type: string, list int, list string, list int) sometimes, but not always, turns the question order back to front, without inverting answer or questionID, which throws it off.
i.e. if the set is questionID 1,2,3 and question A,B,C it sometimes returns 1,2,3 and C,B,A
Can anyone advise why it may be doing this? Why only on the one column? Thanks!
edit: Got it thanks all! In case it helps anyone in future, here is the solution used:
var groupedSet = from row in dataSet
group row by row.UpdatedBy
into grp
select new
{
Survey = grp.Key,
QuestionID = grp.OrderBy(x=>x.QuestionID).Select(i => i.QuestionID),
Question = grp.OrderBy(x=>x.QuestionID).Select(q => q.Text),
Answer = grp.OrderBy(x=>x.QuestionID).Select(a => a.Answer)
};
Reversal of a grouped order is a coincidence: IQueryable<T>'s GroupBy returns groups in no particular order. Unlike in-memory GroupBy, which specifies the order of its groups, queries performed in RDBMS depend on implementation:
The query behavior that occurs as a result of executing an expression tree that represents calling GroupBy<TSource,TKey,TElement>(IQueryable<TSource>, Expression<Func<TSource,TKey>>, Expression<Func<TSource,TElement>>) depends on the implementation of the type of the source parameter.`
If you would like to have your rows in a specific order, you need to add OrderBy to your query to force it.
How I do it and maintain the relative list order, rather than apply an order to the resulting set?
One approach is to apply grouping to your data after bringing it into memory. Apply ToList() to dataSet at the end to bring data into memory. After that, the order of subsequent GrouBy query will be consistent with dataSet. A drawback is that the grouping is no longer done in RDBMS.

How to get a list which contains at least all the values of another list?

I have the situation where a list must contains at least the values of another list. So imagine we have list A with values 1, 2, 3. This is the list with the required values.
List B has the values 1, 5, 6, 7
List C has the values 1, 2, 3, 4, 7
List D has the values 2, 5, 6
In this situation I only want List C, since this is the only list which contains the values 1, 2 end 3.
I've tried this, but this doesn't work since it is always true:
query = from doc in query
let tagIds = from t in doc.Tags select t.Id
where parameters.TagIds.Except(tagIds).Count() <= parameters.TagIds.Count()
select doc;
And when using this:
query = from doc in query
let tagIds = from t in doc.Tags select t.Id
where !parameters.TagIds.Except(tagIds).Any<int>()
select doc;
I only get the lists where the list matches exactly the 'required' list.
My question is, how can I solve my situation in a Linq 2 SQL query?
Try
var query = from doc in query
let tagIds = from t in doc.Tags select t.Id
where parameters.All(p => tagIds.Contains(p))
select doc;
Try this link
Does .NET have a way to check if List a contains all items in List b?
here you can run the same above method in all available lists.
public static bool ContainsAllItems(List<T> a, List<T> b)
{
return !b.Except(a).Any();
}
This checks whether there are any elements in b which aren't in a - and then inverts the result.

LINQ Query To Select Between Unlike Generic Lists

I have two generic lists where I want to run a couple of Linq queries to find out:
Are any of lists A items found in list B
Are all of lists A items found in list B
Here are the lists:
var ListA = new List<long>()
var ListB = new List<MyObject>()
MyObject is defined as:
public class MyObject
{
public long ItemId { get; set; }
// ... Other stuff...
}
I am trying to determine two things (two queries): 1. Do any of the longs in ListA match any of the MyObject.ItemId in ListB? And 2. Can all of the longs in ListA be found in ListB?
ListA and ListB can be different lengths. For number 2, I would need all of ListA's items found in ListB, but not vice-versa. I hope this makes sense.
Thanks,
-Scott
First, you only care about the ItemIds in ListB, so:
var bIDs = ListB.Select(x => x.ItemId);
To answer the first part of your question, I would approach this by finding the intersection of the two lists (the set of all items they share). If it has at least one element in it, then there is overlap between the two.
var sharedIds = ListA.Intersect(bIDs);
if (sharedIds.Any())
// list A contains at least one ItemID which ListB contains
As for the second part, you want to see if list A is a subset of list B. Searching for this, Stack Overflow presents a clean solution:
if (!ListA.Except(bIDs).Any())
// yes, list A is a subset of list B
This snippet works because ListA.Except(bIDs) finds the elements that ListA has that bIDs doesn't. If this is empty, then ListA doesn't contain anything that bIDs doesn't. Thus, everything that is in ListA is also in bIDs.
Here's an example: A = {1, 2}; B = {1, 2, 3}. A is a subset of B. A.Except(B) gives you an empty set - B has both 1 and 2, so can't be in the resulting list, and there isn't anything left in B. So when A is a subset of B, A.Except(B).Any() gives false, as there are no elements in the result; so we obviously negate it if we want to handle that case.
For completeness, if we swap A and B round such that A is not a subset of B: A = {1, 2, 3}; B = {1, 2}, then A.Except(B) gives {3}. It can't contain 1 or 2, because B contains 1 and 2. But B doesn't contain 3, so A.Except(B) can contain it. As {3} contains one element, it isn't empty, so A.Except(B).Any() is true. Negated, it is false if A is not a subset of B.
My explanation is a little terse; if you want to look things up further (and I recommend you do - a little set theory can go a long way), A.Except(B) is LINQ's name for the set difference, or relative set complement. Wikibooks has a decent introduction to set theory if you are so inclined.
var value1 =
(
from itemA in ListA
where ListB.Any(itemB => itemB.ItemID == itemA)
select item
).Count();
var value2 = value1 == ListA.Count();
To just test the conditions, assuming you extract a list of ItemIds into listB:
bool inListA = listA.Any(x => listB.Contains(x));
bool allInListB = listA.All(x => listB.Contains(x));
To test in place without extracting a separate list if ItemIds
bool inListA = listA.Any(x => listB.Select(b => b.ItemId).Contains(x));
bool allInListB = listA.All(x => listB.Select(b => b.ItemId).Contains(x));
If you need to answer all three questions at the same time then it's likely that a pure LINQ solution won't be optimal, since the individual queries will each need to perform the same intersection operation. Do the intersection once, and then use that result to answer your three questions:
var tempSet = new HashSet<long>(ListA);
int uniqueAItemCount = tempSet.Count;
// 2b. "I would need all of ListA's items found in ListB, but not vice-versa."
tempSet.IntersectWith(ListB.Select(x => x.ItemId));
// tempSet now contains all items from ListA also found in ListB
// we can use this result to answer the other two questions...
// 1. "Do any of the longs in ListA match any of the MyObject.ItemId in ListB?"
bool anyAFoundInB = tempSet.Count > 0;
// 2a. "Can all of the longs in ListA be found in ListB?"
bool allAFoundInB = tempSet.Count == uniqueAItemCount;

LINQ Many to Many With In or Contains Clause (and a twist)

I have a many to many table structure called PropertyPets. It contains a dual primary key consisting of a PropertyID (from a Property table) and one or more PetIDs (from a Pet table).
Next I have a search screen where people can multiple select pets from a jquery multiple select dropdown. Let's say somebody selects Dogs and Cats.
Now, I want to be able to return all properties that contain BOTH dogs and cats in the many to many table, PropertyPets. I'm trying to do this with Linq to Sql.
I've looked at the Contains clause, but it doesn't seem to work for my requirement:
var result = properties.Where(p => search.PetType.Contains(p.PropertyPets));
Here, search.PetType is an int[] array of the Id's for Dog and Cat (which were selected in the multiple select drop down). The problem is first, Contains requires a string not an IEnumerable of type PropertyPet. And second, I need to find the properties that have BOTH dogs and cats and not just simply containing one or the other.
Thank you for any pointers.
You can do this using a nested where clause.
You need to filter p.PropertyPets using contains - return all rows where PetID is in search.PetType.
Then only return rows from properties where all search id's have been found - eg number of rows >= number of serach id's
All together:
var result = from p in properties
where p.PropertyPets.Where(c => search.PetType.Contains(c.PetID)).Count() >= search.PetType.Count()
select p;
For the part where Contains requires a string would not be true, Contains should require an int if your search.PetType is int[]. That means that you need to "convert" p.PropertyPets into an int. To convert p.PropertyPets to IEnumerable<int> you need to select the PropertyID field: p.PropertyPets.Select(propertyPet => propertyPet.PropertyID), but that won't get you a single int as required but a whole bunch. (.First() would give you one int but not solve your problem.
What you really want to do is
var result = properties.Where(p =>
search.PetType.Except(p.PropertyPets.Select(propertyPet =>
propertyPet.PropertyID)).Count() == 0);
But Except is not available in LINQ2SQL.
The best option I can find is to apply Contains for each item in search.PetType.
Something like this:
var result = properties;
foreach(var petType in search.PetType)
{
result = from p in result
where p.PropertyPets.Select(propertyPet =>
propertyPet.PropertyID).Contains(petType)
select p;
}

Get the set of grouped values as a list using linq

I am taking a datatable and finding all the rows for a specific key that have fewer than three entries in the table for that key value. I can do this fine and it returns a grouping with the key being the id I want to group on and a list of the datarows that, for each key value, don't exist at least three times. Now I want to get a straight list of all those datarows that failed. I am having trouble doing this. All I can seem to do is get a list of lists.
var rows = from dr in stagingTable.AsEnumerable()
group by dr.Field<long>("KEY_ID") into g
where g.Count() < 3
select new {ID = g.Key, Values = g};
Now that I have the grouped information, I want a straight list of all the datarows that can be found within all the groups. Doing g.ToList() just gives me a list of lists.
Any suggestions?
If you wanted to flatten that list of lists:
var flattened = rows.SelectMany(x=>x.Values).ToList();

Categories

Resources