I have an MVC5 application on which i am working on.
I have a situation in the app where i have to find distinct elements from a list but with a little tweak.
Let me explain the scenario.
I have a Product and Category class:-
**Product**
ProductId
ProducName
CategoryId
CategoryName
**Category**
CategoryId
CategoryName
I have multiple products so i am dealing with a list of Products:-
List<Product> listProducts = new List<Product>();
All the products can be in same category or in multiple categories. So i want to get all the distinct categories list in the category class.
To find the Distinct categories we can use linq as following:-
var listProductCategories = listProducts.Select(x => x.CategoryName).Distinct().ToList();
But this would return a List and i want List with both CategoryId and CategoryName.
Please help me with this
You can group by CategoryId and CategoryName and then project to a new Category item like:
var query = listProducts.GroupBy(r => new { r.CategoryId, r.CategoryName })
.Select(grp => new Category
{
CategoryId = grp.Key.CategoryId,
CategoryName = grp.Key.CategoryName
});
You can add ToList at the end of your query if you want to get a List<Category>
Below is the query based on your questions.
This query will returns distinct records if the list have the similar records.
List<Product> listProducts1 = new List<Product>();
var productNames = listProducts.Select(prod => new { CategoryId = prod.CategoryId, CategoryName = prod.CategoryName }).Distinct();
//This query would returns distict records based on the Category class data.
Category cat=new Category();
var productNames2 = listProducts.Where(prod => prod.CategoryId == cat.CategoryId).Select(prod => new { CategoryId = prod.CategoryId, CategoryName = prod.CategoryName }).Distinct();
Related
I have a table (orders for ex) which has Multiple Columns.
products categories subcategories
--------------------------------------
prod1 cat1 sub1
prod1 cat2 sub2
prod2 cat3 sub6
prod1 cat1 sub1
prod5 cat2 sub8
prod2 cat1 sub1
prod1 cat7 sub3
prod8 cat2 sub2
prod2 cat3 sub1
Now I can write three different queries to get distinct values
var prod = (from p in _context.orders select p.products).ToList().Distinct();
similarly I can write it for others.
Now I need to get the distinct values of each column in a single query for which the result needs to look like
products categories subcategories
--------------------------------------
prod1 cat1 sub1
prod2 cat2 sub2
prod5 cat3 sub6
prod8 cat7 sub8
sub3
My ClassType for unique fields looks like this
public class UniqueProductFields
{
public IEnumerable<string> Products { get; set; }
public IEnumerable<string> Categories { get; set; }
public IEnumerable<string> Subcategories { get; set; }
}
Not sure how to do this in an efficient manner so that I dont have to write three methods. The table is in the database (hence the need for optimization)
Thanks!
Is it an absolutely unchangeable requirement to use Linq? Why do you need it to be returned in a single query?
Suggestion: Use SQL. It can be done in a single query but you won't like the query. I'm assuming SQL Server (can be done differently for other DBMSes).
WITH V AS (
SELECT DISTINCT
V.*
FROM
Orders O
CROSS APPLY (
VALUES (1, O.Products), (2, O.Categories), (3, O.Subcategories)
) V (Which, Value)
),
Nums AS (
SELECT
Num = Row_Number() OVER (PARTITION BY V.Which ORDER BY V.Value),
V.Which,
V.Value
FROM
V
)
SELECT
Products = P.[1],
Categories = P.[2],
Subcategories = P.[3]
FROM
Nums N
PIVOT (Max(N.Value) FOR N.Which IN ([1], [2], [3])) P
;
See this working at db<>fiddle
Output:
Products Categories Subcategories
-------- ---------- -------------
prod1 cat1 sub1
prod2 cat2 sub2
prod5 cat3 sub3
prod8 cat7 sub6
null null sub8
If you are bound and determined to use Linq, well, I can't help you with the query-style syntax. I only know the C# code style syntax, but here's a stab at that. Unfortunately, I don't think this will do you any good, because I had to use some pretty funky stuff to make it work. It uses essentially the same technique as the SQL query above, only, there's no equivalent of PIVOT in Linq and there's no real natural row object other than a custom class.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program {
public static void Main() {
var data = new List<Order> {
new Order("prod1", "cat1", "sub1"),
new Order("prod1", "cat2", "sub2"),
new Order("prod2", "cat3", "sub6"),
new Order("prod1", "cat1", "sub1"),
new Order("prod5", "cat2", "sub8"),
new Order("prod2", "cat1", "sub1"),
new Order("prod1", "cat7", "sub3"),
new Order("prod8", "cat2", "sub2"),
new Order("prod2", "cat3", "sub1")
};
int max = 0;
var items = data
.SelectMany(o => new List<KeyValuePair<int, string>> {
new KeyValuePair<int, string>(1, o.Products),
new KeyValuePair<int, string>(2, o.Categories),
new KeyValuePair<int, string>(3, o.Subcategories)
})
.Distinct()
.GroupBy(d => d.Key)
.Select(g => {
var l = g.Select(d => d.Value).ToList();
max = Math.Max(max, l.Count);
return l;
})
.ToList();
Enumerable
.Range(0, max)
.Select(i => new {
p = items[0].ItemAtOrDefault(i, null),
c = items[1].ItemAtOrDefault(i, null),
s = items[2].ItemAtOrDefault(i, null)
})
.ToList()
.ForEach(row => Console.WriteLine($"p: {row.p}, c: {row.c}, s: {row.s}"));
}
}
public static class ListExtensions {
public static T ItemAtOrDefault<T>(this List<T> list, int index, T defaultValue)
=> index >= list.Count ? defaultValue : list[index];
}
public class Order {
public Order(string products, string categories, string subcategories) {
Products = products;
Categories = categories;
Subcategories = subcategories;
}
public string Products { get; set; }
public string Categories { get; set; }
public string Subcategories { get; set; }
}
I suppose that we could swap this
.Select(i => new {
p = items[0].ItemAtOrDefault(i, null),
c = items[1].ItemAtOrDefault(i, null),
s = items[2].ItemAtOrDefault(i, null)
})
for this:
.Select(i => new Order(
items[0].ItemAtOrDefault(i, null),
items[1].ItemAtOrDefault(i, null),
items[2].ItemAtOrDefault(i, null)
))
Then use that class's properties in the output section.
As far as i know, you won't be able to do it in a single query. Before thinking how would you do it with C# think how would you do it in SQL; I might be wrong but to me you'll be writing 3 querys anyway.
If you notice some performance issues and this is your actual code:
var prod = (from p in _context.orders select p.products).ToList().Distinct();
You may want to start by removing the .ToList() extension method beacuse that is retrieveng all records to memory and only after that the distinction is applied.
That's because your query expression (from p in ...) returns an IQueryable and calling .ToList() on it makes it IEnumerable. force the current formed SQL query to run and bring the results to memory.
The difference in this case is: Deferred execution
See: https://www.c-sharpcorner.com/UploadFile/rahul4_saxena/ienumerable-vs-iqueryable/
I am trying to get the list of gameIds that satisfy all the genreIds included in a a List<int>.
The tables (partial):
editorial_list:
game_id
content
game_genres (game can belong to several genres):
id
game_id
genre_id
I need to get the list of the game Ids of games that exists for all the genre_id's in the game_genres table.
For example:
The list of genre_id's includes genre 2 and 3.
If Game Id = 14 exists in game_genres table for both genre_id = 2 and genre_id = 3. So it will be included in the final results.
Here is my code:
// get the list of game_id's that have an editorial
var editorialList = (from ee in db.editorials where ee.is_enabled == true select new {
game_id = ee.game_id
}).ToList();
// Produce a list of the games in the editorials and their genre Ids that the belong to
var gameAndGenres = (from el in editorialList join gg in db.game_genres
on el.game_id equals gg.game_id
select new {
game_id = el.game_id,
genre_id = gg.genre_id
}
);
var res = gameAndGenres.Where(
x => x.genres.Contains(x.genre_id)) == genres.Count; // stuck here
The end results should be a unique list of game_id's, that each game in the list belongs to all the genres that was listed in the genres List<int>.
I created several steps to help me understand the query, but it might be able to be solved in one line, I just wasn't able to solve it.
Update: This is a new code that I'm trying.
var res = gameAndGenres.GroupBy(x => x.game_id)
.Select(g => new {
game_id = g.Key,
genreIds = g.Select( c => c.genre_id)
});
var res2 = res.Where(x => genres.Intersect(x.genreIds).Count()
== genres.Count()).ToList();
The relation between Game and Genre is a many-to-many: a Game can belong to zero or more genres and a Genre can have zero or more Games.
You want (the IDs of) all games that belong to all Genres in your game-genres table.
For example, if your list game-genres contains only records with references to genre 2 and genre 3 then you want all games that belong to genres 2 and 3.
Note that a Genre may exist that is not owned by any 'Game'. In that case there is no record in the game-genres table with a reference to this Genre.
Below an example of your Property Entity Framework classes. The actual names of the classes and properties may vary, but you'll get the Id
public class Game
{
public int GameId {get; set;}
public virtual ICollection<Genre> Genres {get; set;}
... // other properties
}
public class Genre
{
public int GenreId {get; set;
public virtual ICollection<Game> Games {get; set;}
...
}
public MyDbContext : DbContext
{
public DbSet<Game> Games {get; set;}
public DbSet<Genre> Genres {get; set;}
...
}
The entity framework model builder will detect that there is a many-to-many relation between Games and Genres and will automatically add a table like your 'game-genres'.
The nice thing, is that if a certain Genre does not belong to any Game, it won't be in your game_genres table. Also the other way round: if you have an element in your game-genres table, than there is at least one game that belongs to that genre.
So you don't want all genres, you only want genres that are used by at least one Game.
IEnumerable<Genre> usedGenres = dbContext.Genres
.Where(genre => genre.Games.Any());
Now you want only those Games that belong to EVERY genre in usedGenres
= every game where every element of usedGenres is in the collection Game.Genres.
To check if a genre is in the collection of Game.Genres, we only have to compare the GenreId of Game.Genres with the GenreId of usedGenreIds
IEnumerable<int> usedGenreIds = usedGenres
.Select(genre => genre.GenreId);
IEnumerable<Game> gamesWithAllUsedGenres = dbContext.Games
.Where(game => usedGenreIds.All(genreId => game.Genres.Select(genre => genre.GenreIdContains(genreId));
Something like this:
var gamesIds = db.editorials
.Where(e => db.game_genres.Select(gg => gg.genre_id).Distinct().All(genId => db.game_genres.Any(gg => gg.game_id == e.game_id && gg.genre_id == genId)))
.Select(e => e.game_id)
.ToList();
Select game_id from editorials, where the game_id have an entry of all distinct genre_ids in the game_genre table.
If you want all games having all genres in a list instead of all in the table:
List<int> genreIds = new List<int>() {1,2,3};
var gamesIds = db.editorials
.Where(e => genreIds.All(genId => db.game_genres.Any(gg => gg.game_id == e.game_id && gg.genre_id == genId)))
.Select(e => e.game_id)
.ToList();
You can achieve it by groupping rows from your tables.
Using Lambda expressions:
var res = db.game_genres.GroupBy(gg => gg.game_id)
.Where(g => g.Count() == db.genres.Count())
.Select(x => x.Key).ToList();
or using LINQ:
var res = (from gg in db.game_genres
group gg by gg.game_id into g
where g.Count() == db.genres.Count()
select g.Key).ToList();
Such groupping produces one record (called group) per game_id. The Key of each group is the game_id used in the "group by" clause. Each group is a collection of rows having equal game_id, therefore you can treat it like any other collection of database entities, here you filter it using where statement and call Count() on the collection.
I have a model like this:
public class Post
{
public int PostId,
public List<Category> Categories
}
Posts have at least 1 category, but can also have many categories.
I have a List, this list contains Posts (some with the same PostId), and each entry in the List contains exactly one unique Category (Categories.Count = 1 for each).
I want to create a new List with only distinct Posts (distinct PostId), with the Categories list populated with each category in the original List having the same PostId.
Basically, find each Post in the original list, and populate the Categories field by adding each of their First (and only) entry in their Categories field together.
Is there a nice solution for this in linq?
Category is just an Enum,
I have tried using varous nested foreach and for loops and it works but it is just gross. I know there is a clean way to do it.
Example:
Categories = { PostId = 1, Category = Shopping }, { PostId = 1, Category = Pizza }, { PostId = 2, Category = Laundry }
after sequence desired output to be:
Categories = { PostId = 1, Categories = Shopping, Pizza }, { PostId = 2, Categories = Laundry }
Order does not matter for the category list
Given that you will have only one category per post (as stated in the second paragraph), you can try
var result = aPosts
.GroupBy(item => item.PostId, item => item.Categories[0])
.Select(group => new Post() { PostId = group.Key, Categories = new List<Category>(group) })
.ToList();
Note that having a Post constructor that accepts both PostId and Categories would allow a more simplified version of any solution.
Post(int postId, IEnumerable<Category> categories)
{
PostId = postId;
Categories = new List<Category>(categories);
}
Would allow the following:
var result = aPosts
.GroupBy(item => item.PostId, item => item.Categories[0])
.Select(group => new Post(group.Key, group))
.ToList();
something like below
var result = yourlist.GroupBy(l=>l.PostId)
.Select(x=>new Post{ PostId =x.Key, Categories =x.SelectMany(y=>y.Categories).ToList()})
.ToList();
With LINQ expressions:
var result = from o in posts
group o by o.PostID into gr
select new Post
{
PostID = gr.Key,
Categories = gr.SelectMany(c=>c.Categories).ToList()
};
All the other given solutions would work. But if you might have more than 1 category in the Category list, and you need only the first of each Post you can use following.
var posts =
postList.GroupBy(p => p.PostId)
.Select(
g =>
new Post
{
PostId = g.Key,
Categories =
g.Select(p => p.Categories.FirstOrDefault())
.Where(c => c != null).ToList()
});
Also, make sure you initialize you Categories property (e.g. in the constructor of Post class) before using Linq given in the answers. Otherwise you might get NUllReferenceException.
In my application I have Movements associated with a category.
I want a list of the most frequent category.
My objects are:
Category: catId, catName
Movement: Movid, movDate, movMount, catId
I think it would have to raise it with a "Group By" query (grouping by catId and getting those more)
(Im using Entity Framework 6 in c#)
From already thank you very much!
IMPORTANT: Entity Framework 7 (now renamed to Entity Framework Core 1.0) does not yet support GroupBy() for translation to GROUP BY in generated SQL. Any grouping logic will run on the client side, which could cause a lot of data to be loaded.
https://blogs.msdn.microsoft.com/dotnet/2016/05/16/announcing-entity-framework-core-rc2
group the movements by category and select catid and count.
join this result with category to get the name and then descending sort the results on count.
var groupedCategories = context.Movements.GroupBy(m=>m.catId).Select(g=>new {CatId = g.Key, Count = g.Count()});
var frequentCategories = groupedCategories.Join(context.Categories, g => g.CatId, c => c.catId, (g,c) => new { catId = c.catId, catName = c.catName, count = g.Count }).OrderByDescending(r => r.Count);
foreach (var category in frequentCategories)
{
// category.catId, category.catName and category.Count
}
i hope this help:
var query = dbContext.Category.Select(u => new
{
Cat = u,
MovementCount = u.Movement.Count()
})
.ToList()
.OrderByDescending(u => u.MovementCount)
.Select(u => u.Cat)
.ToList();
I resolved the problem!
I used the proposal by "Raja" solution (Thanks a lot!).
This return a collection composed of "Category" and "Count". I Change it a bit to return a list of Categories.
var groupedCategories = model.Movement.GroupBy(m => m.catId).Select(
g => new {catId= g.Key, Count = g.Count() });
var freqCategories= groupedCategories.Join(model.Category,
g => g.catId,
c => c.catId,
(g, c) => new {category = c, count = g.Count}).OrderByDescending(ca => ca.count).Select(fc => fc.category).ToList ();
you just need to use navigation property on category simply, you have a navigation property on category contains all related Movement, i call it Movements in following query. you can write your query like this, with minimum of connection with DB.
class Cat
{
public Guid catId { get; set; }
public string catName { get; set; }
public IEnumerable<Movement> Movements { get; set; }
public int MovementsCount { get { return Movements.Count(); } }
}
var Categories = category.Select(u => new Cat()
{
u.catId,
u.catName,
Movements = u.Movements.AsEnumerable()
}).ToList();
var CategoriesIncludeCount = Categories.OrderBy(u => u.MovementsCount).ToList();
Afternoon all
I have a lovely webservice call that brings back a list, we'll call it List<Everything>
This would return something along the lines of:
Product ProductName SomethingElse
1 Dave abc
1 Dave def
1 Dave ghi
2 Jason abc
2 Jason def
3 Terry abc
3 Terry def
3 Terry ghi
3 Terry jkl
I then have another List<Products> (int Product, string ProductName) that I would like to populate using the distinct product information in List<Everything>.
So I'm trying to get the following result:
Product Productname
1 Dave
2 Jason
3 Terry
How can I achieve this using Linq?
Apologies for what is probably bloody obvious.
List<Products> products = (from x in everythingList
group x by new { x.Product, x.ProductName } into xg
select new Products
{
Product = xg.Key.Product,
ProductName = xg.Key.ProductName
}).ToList();
var distinctProducts = everything.Select(e=>new { Product, Productname = e.ProductName }).Distinct();
How about this:
List<Everything> items = ...
var results = items.GroupBy(x => new { x.Product, x.ProductName })
.Select(g => new Products()
{
Product = g.Key.Product,
ProductName = g.Key.ProductName
})
.ToList();
You could try to do it like this.
List<Products> products = new List<Products>();
var listEverything = select d in "data from your ws"
select d.Product, d.ProductName).Distinct(x=>x.ProductName);
foreach(var item in listEverything)
{
products.Add(new Products { Product=item.Product, ProductName=item.ProductName});
}
List<Products> products = GetEverythingService()
.Select(p => new Products { Product = p.Product, Productname = p.ProductName})
.Distinct();
As rightly pointed out by dlev in the comments Distinct will only work in this case if the Products class implements the IEqualityComparer<T> interface and overrides the Equals and GetHashCode methods.
This could be overkill if a comparison is only required in this one situation although if product object comparisons are to be carried out elsewhere using the id and product name then it is a viable option. I personally find it a bit more readable than the GroupBy Linq extension but obviously opinions will vary on this.
You can try this.
var unique = list.GroupBy(item => item.Product)
.Select(group => new
{
Product = group.Key,
group.First().ProductName
})
.ToList();