Linq dilemma
I am having trouble efficiently selecting the information that I need to display. Hope someone else understands better how to solve this.
Considering the following data structures,
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public IList<Product> Products{ get; set; }
}
public class Product
{
public int ID { get; set; }
public string Name { get; set; }
}
And given the following data
Department1 =
{
Id=1,
Name="D1",
Products = {new Product{Id=1, Name="Item1"}, new Product{Id=2, Name="Item2"}
}
Department2 =
{
Id=2,
Name="D2",
Products = {new Product{Id=2, Name="Item2"}, new Product{Id=3, Name="Item3"}
}
How to choose that "Item2" is common to "D1" and "D2"?
I tried using an intersection query, but it seems to require that the two deferred query plans intersect, not two IEnumerables or ILists.
Any help with this is greatly appreciated.
Edit: Looks like I wasn't very precise trying to keep things simple.
I have a list of departments, each of which contains a list of products. Given these listings, how to choose a different list of products based on certain criteria. My criteria in this case is that I want to select only those products that exist in all my departments. I only want data that traverses all the elements.
a source to share
My Intersect works fine:
Department department1 = new Department
{
Id = 1,
Name = "D1",
Products = new List<Product> () { new Product { Id = 1, Name = "Item1" }, new Product { Id = 2, Name = "Item2" } }
};
Department department2 = new Department
{
Id = 2,
Name = "D2",
Products = new List<Product>() { new Product { Id = 2, Name = "Item2" }, new Product { Id = 3, Name = "Item3" } }
};
IEnumerable<Product> products = department1.Products.Intersect(department2.Products, new ProductComparer());
foreach (var p in products)
{
Console.WriteLine(p.Name);
}
(edit)
public class ProductComparer : IEqualityComparer<Product>
{
public bool Equals(Product x, Product y)
{
return x.Name == y.Name && x.Id == y.Id;
}
public int GetHashCode(Product obj)
{
return obj.Id.GetHashCode() ^ obj.Name.GetHashCode();
}
}
a source to share
If you want to use Linq, you can flatten the collection of products and link them to the parent of the object (so that you have a collection of (Product, Department) pairs) and then regroup to Product.
var sharedItems = new[] { department1, department2 }
.SelectMany(d => d.Products, (dep, prod) => new { Department = dep, Product = prod })
.GroupBy(v => v.Product)
.Where(group => group.Count() > 1);
The result of this query is an IGroupings enumeration, where the key is a product and contains sections with that product.
a source to share
// Get a list of all departments.
IEnumerable<Department> departments = GetAllDepartments();
// Get a list of all products.
var products = departments.SelectMany(d => d.Products).Distinct();
// Filter out all products that are not contained in all departments.
var filteredProducts = products.
Where(p => departments.All(d => d.Products.Contains(p)));
If you combine both queries, you get the following.
var filteredProducts = departments.
SelectMany(d => d.Products).
Distinct().
Where(p => departments.All(d => d.Products.Contains(p)));
a source to share
Project each department into IEnumerable<Product>
. Aggregate Product Lists — Use the first as a starting point and Intersect on the rest of the lists.
List<Product> commonProducts = departments
.Select(d => d.Products.AsEnumerable() )
.Aggregate( (soFar, nextList) => soFar
.Intersect(nextList, productComparer) )
.ToList()
You would need to implement ProductComparer to get around referential equality - if Product was a struct, you wouldn't need to do this.
a source to share