I am trying to understand the group by keyword and I made sample data sets.
If I want to group by name, then I lose product ID. So I couldn't complete the issues.
Data Structure
Product=[
{id: 1, name:"desktop", productCategoryId:1},
{id: 2, name:"laptop", productCategoryId:1},
{id: 3, name:"desktop", productCategoryId:1},
{id: 4, name:"laptop", productCategoryId:1},
{id: 5, name:"desktop", productCategoryId:1},
];
ProductCategory =[
{productCategoryId: 1, name:"desktop"},
{productCategoryId: 2, name:"Electronics"},
{productCategoryId: 3, name:"Furniture"},
];
If you want to group by Product Name and want to get the result as below, what linq query can I use?
Result I want
Result = [
{
id: 1, name: "desktop", productCategoryId:1,
},
{
id: 2, name: "laptop", productCategoryId:1,
},
]
Your requirement has some shortcomings. I don't know why you would use Product1 in your end result and not Product[3]. Furthermore: all Products have ProductCategory 1. What if some of them have ProductCategory [2]?
So all I can tell you how to use GroupBy.
If you have a sequence of similar items, and you want to group these items into groups that have something in common, you can use one of the overloads of [Enumerable.GroupBby].1.
You'll have to say what the items in one group have in common. This is done using parameter KeySelector. It has the following format:
IEnumerable<Product> products = ...
var productsWithSameName = product.GroupBy(
// KeySelector: make groups of Products with the same Name:
product => product.Name,
...
);
The part product => product.Name is the keySelector. It means: from every element of your input sequence, take property Name.
By the way: to make it easier to read, convention is to use plural nouns to identify collections, and use singular nouns to identify elements of the collection. So the collection is a collection of Products, one element of the collection is identified by parameter product.
So, from every product in the collection of Products, we take the value of property Name. We put all Products with the same Name into groups. Every group will have a Key, which is the value of the item that all elements in the group have in common.
In our case, all elements of the group with Key == "laptop" will have a value of property Name equal to "laptop".
On the left side of the => is one Product, on the right side of the => is the object that we will use to group the items on. This can be a simple property value, but it can also be composed from several properties:
If you want to make groups of families that live in the same country with the same number of children, your keySelector will be:
family => family => new
{
Country = family.Country
ChildrenCount = family.Childrend.Count(),
},
This will give you the following groups:
USA families without Children
USA families with one Child
USA families with two Children
Indian families without Children
Indian families with one Child
Dutch families without Children
etc.
You will not have groups without elements: every group will have at least one element. All elements in the group will have the same value for Key. The group is identified by this Key. Every group will have a unique key, otherwise these groups should have been combined in one group.
If you use the overload of GroupBy that has only a keySelector, you will make groups of Products that have the same key:
IEnumerable<Product> products = ...
var groupsOfProductsWithSameName = products.GroupBy(product => product.Name);
foreach (var group in groupsOfProductsWithSameName
{
string commonName = group.Key;
IEnumerable<Product> productsWithThisName = group;
foreach (product in productsWithThisName)
{
Assert(product.Name == group.Key);
}
}
So every group will have a Key, which is the result of the keySelector. Every element of the group will be a product that has a Name equal to the Key of the group.
Nota Bene: the group IS a sequence, not HAS a sequence!
One of the overloads of GroupBy has an extra parameter: resultSelector. It takes the form:
(key, productsWithThisKey) => ...
This means: from every key that we found, and all productsWithThisKey, do the following ...
So for instance: we make groups of products with the same name, and from every group we want to remember the name, the number of products in the group and the lowest 10 Ids of the products in the group as the first page of products.
IEnumerable<Product> products = ...
var firstProductPage = products.GroupBy(
// parameter keySelector: make groups with same Name
product => product.Name,
// parameter resultSelector:
// for every Name that we found, and all products with this name, make one new:
(name, productsWithThisName) => new
{
Name = name,
NumberOfProducts = productsWithThisName.Count(),
Ids = productsWithThisName.Select(product => product.Id)
.Take(10)
.ToList(),
});
With parameter resultSelector you can define exactly what should be in each Group.
Related
I am trying to learn how to use LINQ to perform a query that yields the same result as this:
SELECT (
SELECT SUM(point)
FROM communitymemberpointfeature
WHERE communitymemberpointfeature.communitymemberid = communitymember.id
) AS points, communitymember.*
FROM communitymember
After browsing around the Internet, I constructed the following statement:
var list = (from pointFeature in communityMemberPointFeatureList
join member in communityMemberList on pointFeature.CommunityMemberId equals member.Id
group pointFeature by new { pointFeature.CommunityMemberId }
into grouping
select new
{
grouping,
points = grouping.Sum(row => row.Point)
}).ToList();
But this yielded a result like
[
{
points:7200,
grouping:[
{Id:1,Point:5000,FeatureId:1,CommunityMemberId:1},
{Id:2,Point:2200,FeatureId:1,CommunityMemberId:1},
],
}
...
]
What I really want is a result set like:
[
{points:7200,CommunityMemberId:1,firstname:'john',lastname:'blah' ....},
...
]
Can someone tell me what I did wrong?
Edit after comment added to the end
I can imagine you have problems translating your SQL into LINQ. When trying to write LINQ statements it is usually a lot easier to start from your requirements, instead of starting from a SQL statement.
It seems to me that you have a table with CommunityMembers. Every CommunityMember has a primary key in property Id.
Furthermore, every CommunityMember has zero or more CommunityMemberPointFeatures, namely those CommunityMemberPointFeatures with a foreign key CommunityMemberId that equals the primary key of the CommunityMember that it belongs to.
For example: CommunityMember [14] has all CommunityMemberPointFeatures that have a value CommunityMemberId equal to 14.
Requirement
If I look at your SQL, it seems to me that you want to query all CommunityMembers, each with the sum of property Point of all CommunityMemberPointFeatures of this CommunityMember.
Whenever you want to query "items with their zero or more subitems", like "Schools with their Students", "Customers with their Orders", "CommunityMembers with their PointFeatures", consider using GroupJoin.
A GroupJoin is in fact a Left Outer Join, followed by a GroupBy to make Groups of the Left item with all its Right items.
var result = dbContext.CommunityMembers // GroupJoin CommunityMembers
.GroupJoin(CommunityMemberPointFeatures, // With CommunityMemberPointFeatures
communityMember => communityMember.Id, // from every CommunityMember take the Id
pointFeature => pointFeature.CommunityMemberId, // from every CommunityMemberPointFeature
// take the CommunityMemberId
// Parameter ResultSelector: take every CommunityMember, with all its matching
// CommunityMemberPointFeatures to make one new object:
(communityMember, pointFeaturesOfThisCommunityMember) => new
{
// Select the communityMember properties that you plan to use:
Id = communityMember.Id,
Name = communityMember.Name,
...
// From the point features of this CommunityMember you only want the sum
// or property Point:
Points = pointFeaturesOfThisCommunityMember
.Select(pointFeature => pointFeature.Point)
.Sum(),
// However, if you want more fields, you can use:
PointFeatures = pointFeaturesOfThisCommunityMember.Select(pointFeature => new
{
Id = pointFeature.Id,
Name = pointFeature.Name,
...
// not needed, you know the value:
// CommunityMemberId = pointFeature.CommunityMemberId,
})
.ToList(),
});
Edit after comment
If you want, you can omit Selecting the values that you plan to use.
// Parameter ResultSelector:
(communityMember, pointFeaturesOfThisCommunityMember) => new
{
CommunityMember = communityMember,
PointFeatures = pointFeaturesOfThisCommunityMember.ToList(),
),
However, I would strongly advise against this. If CommunityMember [14] has a thousand PointFeatures, then every PointFeature will have a foreign key with a value 14. So you are transporting this value 14 1001 times. What a waste of processing power, not to mention all the other fields you plan not to use.
Besides: if you do this you violate against information hiding: whenever your tables changes internally, the result of this function changes. Is that what you want?
I have a list of Items and every item have some list, Now I wants to select Distinct items of child. I have tried like below but it's not working.
var items = await _context.Items.
Include(i => i.Tags.Distinct()).
Include(i => i.Comments).
OrderBy(i => i.Title).ToListAsync();
//Tag items
TagId - tag
------------------
1 --- A
2 --- B
3 --- B
4 --- C
5 --- D
6 --- D
7 --- F
//Expected Result
Item.Tags -> [A,B,C,D,F]
how can I do this in EF Core? Thanks.
You can use the MoreLinq library to get DistinctBy or write your own using this post.
Then use this:
var items = await _context.Items.
Include(i => i.Tags).
Include(i => i.Comments).
OrderBy(i => i.Title).
DistinctBy(d => d.Tags.tag).
ToListAsync();
You want to get distinct records based on one column; so that should do it.
Apparently you have a table of Items, where every Item has zero or more Tags. Furthermore the Items have a property Comments, of which we do not know whether it is one string, or a collection of zero or more strings. Furthermore every Item has a Title.
Now you want all properties of Items, each with its Comments, and a list of unique Tags of the items. Ordered by Title
One of the slower parts of database queries is the transport of the selected data from the database management system to your local process. Hence it is wise to limit the amount of data to the minimum you are really using.
It seems that the Tags of the Items are in a separate table. Every Item has zero or more Tags, every Tag belongs to exactly one item. A simple one-to-many relation with a foreign key Tag.ItemId.
If Item with Id 300 has 1000 Tags, then you know that every one of these 1000 Tags has a foreign key ItemId of which you know that it has a value of 300. What a waste if you would transport all these foreign keys to your local process.
Whenever you query data to inspect it, Select only the properties
you really plan to use. Only use Include if you plan to update the
included item.
So your query will be:
var query = myDbContext.Items
.Where(item => ...) // only if you do not want all items
.OrderBy(item => item.Title) // if you Sort here and do not need the Title
// you don't have to Select it
.Select(item => new
{ // select only the properties you plan to use
Id = item.Id,
Title = item.Title,
Comments = item.Comments, // use this if there is only one item, otherwise
Comments = item.Comments // use this version if Item has zero or more Comments
.Where(comment => ...) // only if you do not want all comments
.Select(comment => new
{ // again, select only the Comments you plan to use
Id = comment.Id,
Text = comment.Text,
// no need for the foreign key, you already know the value:
// ItemId = comment.ItemId,
})
.ToList();
Tags = item.Tags.Select(tag => new
{ // Select only the properties you need
Id = tag.Id,
Type = tag.Type,
Text = tag.Text,
// No need for the foreign key, you already know the value
// ItemId = tag.ItemId,
})
.Distinct()
.ToList(),
});
var fetchedData = await query.ToListAsync();
I haven't tried it, but I'd say you put .Distinct() in the wrong place.
var items = await _context.Items
.Include(i => i.Tags)
.Include(i => i.Comments).
.OrderBy(i => i.Title)
.Select(i => { i.Tags = i.Tags.GroupBy(x => x.Tag).Select(x => x.First()); return i; })
.ToListAsync();
I guess there must be an easy way, but not finding it. I would like to check whether a list of items, appear (completely or partially) in another list.
For example: Let's say I have people in a department as List 1. Then I have a list of sports with a list of participants in that sport.
Now I want to count, in how many sports does all the people of a department appear.
(I know some tables might not make sense when looking at it from a normalisation angle, but it is easier this way than to try and explain my real tables)
So I have something like this:
var peopleInDepartment = from d in Department_Members
group d by r.DepartmentID into g
select new
{
DepartmentID = g.Key,
TeamMembers = g.Select(r => d.PersonID).ToList()
};
var peopleInTeam = from s in Sports
select new
{
SportID = s.SportID,
PeopleInSport = s.Participants.Select(x => x.PersonID),
NoOfMatches = peopleInDepartment.Contains(s.Participants.Select(x => x.PersonID)).Count()
};
The error here is that peopleInDepartment does not contain a definition for 'Contains'. Think I'm just in need of a new angle to look at this.
As the end result I would like print:
Department 1 : The Department participates in 3 sports
Department 2 : The Department participates in 0 sports
etc.
Judging from the expected result, you should base the query on Department table like the first query. Maybe just include the sports count in the first query like so :
var peopleInDepartment =
from d in Department_Members
group d by r.DepartmentID into g
select new
{
DepartmentID = g.Key,
TeamMembers = g.Select(r => d.PersonID).ToList(),
NumberOfSports = Sports.Count(s => s.Participants
.Any(p => g.Select(r => r.PersonID)
.Contains(p.PersonID)
)
)
};
NumberOfSports should contains count of sports, where any of its participant is listed as member of current department (g.Select(r => r.PersonID).Contains(p.PersonID))).
I have two SQL tables: Movies and Tags, while Movies contains a List<Tag>
Now I want to find all movies that have at least one of the tags of a List<Tag> argument. How can I do that in LINQ?
public IEnumerable<Group<Genre, Movie>> GetMoviesGrouped(string search, List<Tag> tags)
{
var movies = from movie in Movies
where ( movie.Name.Contains(search)) && movie.Tags.???contains any element of tags???
group movie by movie.genre into g
select new Group<Genre, Movie> { Key = g.Key, Values = g };
....
}
Movies where any of the tags is contains in tags:
var movies = from movie in Movies
where ( movie.Name.Contains(search))
&& movie.Tags.Any(t => tags.Contains(t))
group movie by movie.genre into g
select new Group<Genre, Movie> { Key = g.Key, Values = g };
However since this is comparing Tag instances and not strings:
It probably won't get translated to SQL (which means you will have to hydrate the query and do the extra filtering in Linq-to-Objects), and
You may want to compare the data of the tags rather than instances (unless you have overridden Equals on Tag
something like:
where movie.Tags.Any(mt => tags.Any(t => t.ID == mt.ID)) // or whatever property(ies) of `Tag` defines equality
You can intersect both list and see if there is any element like:
&& movie.Tags.Intersect(tags).Any()
Since Tag is an object, It will compare the references. Instead you can select unique identifier from Tag and then intersect that like:
&& movie.Tags.Select(r=> r.ID).Intersect(tags.Select(t=> t.ID)).Any()
Where ID is the primary key of Tag table.
I have a generic list declared as so:
List<Dictionary<string, int>> sales;
The string will be a product name and the int the number of units sold. I want to group by productname and sum the total sales. So i can see the number of units sold for each product.
How would i do this using linq?
Thanks!
This will give you the result as a dictionary where the key is the product name and the value is the total number of units sold.
var result =
sales.SelectMany(d => d) // Flatten the list of dictionaries
.GroupBy(kvp => kvp.Key, kvp => kvp.Value) // Group the products
.ToDictionary(g => g.Key, g => g.Sum()); // Sum each group