tags:

views:

176

answers:

7
+1  Q: 

Linq Dilemma

I am having trouble efficiently selecting the information I need to display. Hopefully someone else has a better idea of how to solve this.

Given 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 do I select out that "Item2" is common to both "D1" and "D2"?

I have tried using an intersection query, but it appears to want two deferred query execution plans to intersect rather than two IEnumerable lists or ILists.

Any help with this is greatly appreciated.

Edit: It appears I wasn't very precise by trying to keep things simple.

I have a list of departments, each contain a list of products. Given these lists, how do I select another list of products based on a particular criteria. My criteria in this instance is that I want to select only products that exist in all of my departments. I want only the data that intersects all elements.

A: 

Maybe I am not understanding the question completely but would this work?

var commonProducts = new List<Product>();

Department1.Products.ForEach(delegate(Product product)
{
    if (Department2.Products.Contains(product))
    {
     commonProducts.Add(product);
    }
});
J.13.L
I had to change your IList<Product> to a List to get the ForEach method... If you wanted to keep the IList then you could just use the foreach statement...
J.13.L
+1  A: 

My Intersect function 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();
        }
    }
bruno conde
How did you implement the comparer?
Ty
Thanks Bruno, I managed to get the intersection working and will stick with it as it seems the easiest solution to follow.
Ty
A: 

Use SelectMany to generate a flattened list of product/department pairs (probably in an anonymous class), then use GroupBy to group by Product Name (and handle e.g. multiple instances of the same product in the same department).

Will expand if I get a chance later.

Ruben Bartelink
A: 

The LINQ query syntax is a better fit for these types of queries:

var q = from p1 in Department1.Products
        join p2 in Department2.Products
        on p1.Id equals p2.Id
        select p1;

foreach (Product p in q) Console.WriteLine(p.Name);
Jason
+1  A: 

If you want to use Linq, then you could flatten the Products collection and associate each one with the parent Department object (so you have a collection of (Product, Department) pairs) and then re-group on 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 enumeration of IGroupings where the key is the product and contains the Departments with that product.

Lee
+1  A: 
// 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 merge both queries, you get the following.

var filteredProducts = departments.
    SelectMany(d => d.Products).
    Distinct().
    Where(p => departments.All(d => d.Products.Contains(p)));
Daniel Brückner
Couldn't quite get this to work, but it did lead me down the right path. Thanks Daniel.
Ty
It will (probably) work with LINQ to Object, but I assume it doesn't fit for LINQ to SQL and LINQ to Entity without some modifications (and I assume you use one of this OR mappers).
Daniel Brückner
A: 

Project each department into an IEnumerable<Product>. Aggregate the product lists- use the first one as a starting point and Intersect in the remaining lists.

List<Product> commonProducts = departments
  .Select(d => d.Products.AsEnumerable() )
  .Aggregate( (soFar, nextList) => soFar
    .Intersect(nextList, productComparer) )
  .ToList()

You'll have to implement a ProductComparer to get around reference equality - if Product was a struct, you wouldn't have to do this.

David B