I have a list that contain following type of objects
public class A {
public List<B> B {get;set;}
}
The B class state as follows.
public class B {
public List <C> C { get;set;}
}
The C class state as follows.
public class C {
public int Id { get ; set ;}
public string Name { get; set;}
}
Can someone point me how can I group above list by Name property in C class.
var results = a.SelectMany(a1 => a1.B.SelectMany(b1 => b1.C)
.GroupBy(g => g.Name)
.Select(g => new { Name = g.Key, Count = g.Count() }));
It should return a list of objects containing a Name property for each unique name found, along with a Count property specifying how many of that Name were grouped.
The nested calls to SelectMany will flatten the object hierarchy, the GroupBy will create the grouping on the Name property. Finally, a new anonymous type List is returned though the Select and is populated with properties from the group, with g.Key being the value the grouping was based on (C.Name).
A Fiddle can be found Here (Provided by #chadalavada harish)
I try to get the count of loan by Book Type.
i have these 3 class (Simplified). part of a Code-first Model :
public class Loan
{
public int LoanId {get;set;}
.....
public int BookId {get;set;}
Public virtual Book {get;set;}
}
//Book parent class
public class Book {
public int BookId {get;set;}
...
}
//a Book child class with a specific 'Type' property
public SmallBook : Book
{
public string Type {get;set;}
...
}
So long, i tried this kind of Query ....
var StatsMono = (from p in context.Loans
//the 'where' clause allow to obtain all the loans where Loans.Book is a SmallBook.
where context.Books.OfType<SmallBook>().Any(exm => exm.BookId == p.BookId)
//here is my problem : i can't access 'SmallBook.Type' w/o cast
group p by ((SmallBook)p.Book).Type into g
select { GroupingElement=g.Key,intValue=g.Count()}
).ToList();
...but i can't get rid of the following exception:
Unable to cast the type 'Ips.Models.Book' to type
'Ips.Models.SmallBook'. LINQ to Entities only supports casting EDM
primitive or enumeration types.
I understand why i get this error but now i'm wondering if there is a way to achieve what i want with only one query ?
You can use explicit join:
var StatsMono = (from p in db.Loans
join b in db.Books.OfType<SmallBook>() on p.BookId equals b.BookId
group p by b.Type into g
select new { GroupingElement = g.Key, intValue = g.Count() }
).ToList();
But better add the inverse navigation property to your model
public abstract class Book
{
public int BookId { get; set; }
// ...
public ICollection<Loan> Loans { get; set; }
}
and use it
var StatsMono = (from b in db.Books.OfType<SmallBook>()
from p in b.Loans
group p by b.Type into g
select new { GroupingElement = g.Key, intValue = g.Count() }
).ToList();
Something like..
var result = context.Loans.GroupBy(g=> g.book.Type).select(s=> new { BookType= s.book.type, count = s.count }).ToList();
I'm trying to group by CategoryId and display the CategoryId + the Name property of the group, I need help modifying this code so the Name property can be displayed, check out view below th see what I mean.
Database
ItemCategory
int ItemId
int CategoryId
Category
int CategoryId
int? ParentId
string Name
var itemcategories = db.Fetch<ItemCategory, Category>(#"SELECT * FROM ItemCategory LEFT JOIN Category on Category.CategoryId = ItemCategory.CategoryId WHERE ItemId = #0", item.ItemId);
var grouped = from b in itemcategories
where b.Category.ParentId != null
group b by b.Category.ParentId ?? 0 into g
select new Group<int, ItemCategory> { Key = g.Key, Values = g };
public class Group<K,T>
{
public K Key;
public IEnumerable<T> Values;
}
In view
#foreach (var group in #Model.ItemCategories)
{
#group.Key **Category.Name should be displayed here**
}
foreach (var value in group.Values)
{
#value.Category.Name
}
I think you're looking for the answer provided here:
Group by in LINQ.
However, you should also change your DB query so that it returns the actual result of the join and not just an IEnumerable<ItemCategory>.
The Group class could look like:
public class Group<K,T>
{
public K Key;
public IEnumerable<T> Values;
public IEnumerable<string> CategoryNames;
}
Note that if you want to group by ParentId, your key will always be ParentId, it's the idea of the GroupBy function.
With that new Group class and DB query, you should be able to use g.Name as the parameter for CategoryNames.
I using Linq (together with EF) in order to access my database. I have object "Job", which contains several properties, some of them are "complex". My goal is to group jobs by these properties, and to get a count for each group.
Here my objects (simplified):
public class Job
{
[Key]
public int Id
{
get;
set;
}
[Required]
public Salary Salary
{
get;
set;
}
[Required]
public ICollection<Category> Categories
{
get;
set;
}
}
"Category" is a complex class, and looks like this:
public class Category
{
[Key]
public int Id
{
get;
set;
}
public Industry Industry //Example: Software
{
get;
set;
}
public Field Field //Example: .NET
{
get;
set;
}
public Position Position //Example: Developer
{
get;
set;
}
}
Industry, Field, Position and Salary classes contains just "int" id and "string" name.
I need to group list of Jobs by Industry, Field, Position and Salary and to get a count of each group. This is how I doing it right now:
var IndustryGroupsQuery = from t in Jobs.SelectMany(p => p.Categories)
group t by new { t.Industry} into g
select new
{
Tag = g.Key.Industry,
Count = g.Count()
};
var FieldsGroupsQuery = from t in Jobs.SelectMany(p => p.Categories)
group t by new { t.Field} into g
select new
{
Tag = g.Key.Field,
Count = g.Count()
};
var PositionsGroupsQuery = from t in Jobs.SelectMany(p => p.Categories)
group t by new { t.Position} into g
select new
{
Tag = g.Key.Position,
Count = g.Count()
};
Jobs.GroupBy(job => job.Salary)
.Select(group => new
{
Tag = group.Key,
Count = group.Count()
}))
This is works fine, but I wondering is it possible to improve somehow its performance.
Q1: I think, that probably one single query will perform better that four. Is it possible to combine these queries into one single query?
Q2: When I asking Linq to group by "Industry", how exactly it able to distinguish between one Industry to another? Is it implicitly comparing records' keys? Is it will be faster if I explicitly tell to linq which property to group by (e.g. "id") ?
Thanks!
Answer in reverse order:
Q2:
When you group by an object instead of a base type, it uses the standard equality comparer (obj x == obj y) which does a simple reference comparison (http://msdn.microsoft.com/en-us/library/bsc2ak47(v=vs.110).aspx). If that suits, it works, otherwise you can implement a custom equality comparer (How to implement IEqualityComparer to return distinct values?)
Q1:
If you wanted sub-groups of the groups, then you can do it in a single query. If you just want the counts for each, then you are doing it exactly the right way.
You can user conditional GROUP BY.
You can define a variable to tell the query which column to use for grouping. You can define an ENUM for GROUP BY columns.
int groupByCol = 1; //Change the value of this field according to the field you want to group by
var GenericGroupsQuery = from t in Jobs
group t by new { GroupCol = ( groupByCol == 1 ? t.Industry:(groupByCol == 2 ? t.Field:(groupByCol == 3 ? t.Position : t.Job)))} into g
select new
{
Tag = g.Key,
Count = g.Count()
};
From what I understand from the documentation of SelectMany, one could use it to produce a (flattened) sequence of a 1-many relationship.
I have following classes
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public string Description { get; set; }
}
I then try to use them using the query expression syntax like so
var customers = new Customer[]
{
new Customer() { Id=1, Name ="A"},
new Customer() { Id=2, Name ="B"},
new Customer() { Id=3, Name ="C"}
};
var orders = new Order[]
{
new Order { Id=1, CustomerId=1, Description="Order 1"},
new Order { Id=2, CustomerId=1, Description="Order 2"},
new Order { Id=3, CustomerId=1, Description="Order 3"},
new Order { Id=4, CustomerId=1, Description="Order 4"},
new Order { Id=5, CustomerId=2, Description="Order 5"},
new Order { Id=6, CustomerId=2, Description="Order 6"},
new Order { Id=7, CustomerId=3, Description="Order 7"},
new Order { Id=8, CustomerId=3, Description="Order 8"},
new Order { Id=9, CustomerId=3, Description="Order 9"}
};
var customerOrders = from c in customers
from o in orders
where o.CustomerId == c.Id
select new
{
CustomerId = c.Id
, OrderDescription = o.Description
};
foreach (var item in customerOrders)
Console.WriteLine(item.CustomerId + ": " + item.OrderDescription);
This gives to what I need.
1: Order 1
1: Order 2
1: Order 3
1: Order 4
2: Order 5
2: Order 6
3: Order 7
3: Order 8
3: Order 9
I assume this translates to using the SelectMany method when not using the query expression syntax?
Either ways, I'm trying to wrap my head around using SelectMany. So even if my above query does not translate to SelectMany, given the two classes and mock data, could someone provide me with a linq query that uses SelectMany?
Here is your query using SelectMany, modeled exactly after your example. Same output!
var customerOrders2 = customers.SelectMany(
c => orders.Where(o => o.CustomerId == c.Id),
(c, o) => new { CustomerId = c.Id, OrderDescription = o.Description });
The first argument maps each customer to a collection of orders (completely analagous to the 'where' clause you already have).
The second argument transforms each matched pair {(c1, o1), (c1, o2) .. (c3, o9)} into a new type, which I've made the same as your example.
So:
arg1 maps each element in the base collection to another collection.
arg2 (optional) transforms each pair into a new type
The resulting collection is flat like you'd expect in your original example.
If you were to omit the second argument, you would end up with a collection of all orders the match up to a customer. It'd be just that, a flat collection of Order objects.
Using it takes a lot of getting used to, I still have trouble wrapping my head around it sometimes. :(
SelectMany() works like Select, but with that extra feature of flattening a collection that is selected. It should be used whenever you want a projection of elements of sub-collections, and don't care about the sub-collection's containing element.
For example, let's say your domain looked like this:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public List<Order> Orders { get; set; }
}
class Order
{
public int Id { get; set; }
public Customer Customer { get; set; }
public string Description { get; set; }
}
To get the same list you wanted, your Linq would look something like this:
var customerOrders = Customers
.SelectMany(c=>c.Orders)
.Select(o=> new { CustomerId = o.Customer.Id,
OrderDescription = o.Description });
... which will produce the same result without needing the flat collection of Orders. The SelectMany takes each Customer's Orders collection and iterates through that to produce an IEnumerable<Order> from an IEnumerable<Customer>.
Though this is an old question, I thought I would improve the excellent answers a little:
SelectMany returns a list (which may be empty) for each element of the controlling list. Each element in these result lists are enumerated into the expressions' output sequence and so are concatenated into the result. Hence, a' list -> b' list[] -> concatenate -> b' list.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
using System.Diagnostics;
namespace Nop.Plugin.Misc.WebServices.Test
{
[TestClass]
public class TestBase
{
[TestMethod]
public void TestMethod1()
{ //See result in TestExplorer - test output
var a = new int[]{7,8};
var b = new int[]
{12,23,343,6464,232,75676,213,1232,544,86,97867,43};
Func<int, int, bool> numberHasDigit =
(number
, digit) =>
( number.ToString().Contains(digit.ToString()) );
Debug.WriteLine("Unfiltered: All elements of 'b' for each element of 'a'");
foreach(var l in a.SelectMany(aa => b))
Debug.WriteLine(l);
Debug.WriteLine(string.Empty);
Debug.WriteLine("Filtered:" +
"All elements of 'b' for each element of 'a' filtered by the 'a' element");
foreach(var l in a.SelectMany(aa => b.Where(bb => numberHasDigit(bb, aa))))
Debug.WriteLine(l);
}
}
}
Here is another option using SelectMany
var customerOrders = customers.SelectMany(
c => orders.Where(o => o.CustomerId == c.Id)
.Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));
If you use the Entity Framework or LINQ to Sql and you have an association (relationship) between the entities, then you can do so:
var customerOrders = customers.SelectMany(
c => c.orders
.Select(p => new {CustomerId = c.Id, OrderDescription = p.Description}));