Search code examples
c#linq

LINQ IntersectBy complex query


I am trying to write a fairly complex query. Here is what I have:

public class ClassA
{
    public ICollection<ClassB> classBObjects { get; set; }
}

public class ClassB
{
    public string Name { get; set; }
    public string Value { get; set; }
    public bool IsVisible { get; set; }
}

While getting the list of ClassA objects, I need to check the collection property (classB objects) against IEnumerable params and only get those ClassA objects along with the list of ClassB objects that have a match.

So if I have :

var objectAs = new[]
{
    new ClassA
    {
        classBObjects = new[]
        {
            new ClassB { Name = "X", Value = "1", IsVisible = true },
            new ClassB { Name = "Y", Value = "2", IsVisible = true },
            new ClassB { Name = "Z", Value = "3", IsVisible = true },
        },
    },
    new ClassA
    {
        classBObjects = new[]
        {
            new ClassB { Name = "W", Value = "4", IsVisible = true },
            new ClassB { Name = "X", Value = "1", IsVisible = true },
        },
    },
};

var parameters = new[]
{
    new { name = "X", value = "1", },
    new { name = "Y", value = "2" },
};

I should get back:

 objectA1
      objectB1
      objectB2
 

I am wondering if IntersectBy may help me achieve the result I am looking for but not quite sure how to form the query.

Also, I believe this will have to be in-memory computation:

var objectAList = context.ClassA.AsEnumerable();

so probably not so performant?

EDIT: Thanks for the answers and apologies for the vagueness of my previous description. I will try and rectify it. What I need is a list of ClassA objects which has a matching ClassB for ALL parameters. So, for the above example, the result should actually be:

objectA1
  objectB1
  objectB2

The following (based off of one of the posted answers) would work if the requirement was to get a list with at least a parameter match but I need all parameters to match so trying to come up with a query that will work.

var result= classAObjects.SelectMany(a => a.classBObjects.Where(b =>
parameters.Any(p => p.Name == b.Name && p.Value == b.Value)
)), (classAObject, classBObject) => new { classAObject }).ToList();

Also, someone had asked about the involvement of Entity Framework. So basically, I get the data from the database. However, I don't think it is possible to write a LINQ-to-Entities query that involves my parameters list so I first fetch classAObjects collection from database and then perform the query on it.

Given below is the solution I came up with:

  var results = classAObjects
 .Select(a =>
   new
   {
     a,
     match = parameters.All(p => a.classBOjects.Any(
         b => b.Name == p.Name && b.Value == p.Value
       ))
   }
 ).Where(r => r.match.Equals(true)).Select(r => r.a)
 .ToList();

Update: jdweng's solution helped me figure out the solution so I am going to go ahead and mark that as the answer.


Solution

  • Try following :

       class Program
        {
            static void Main(string[] args)
            {
                List<ClassA> objects = new List<ClassA> {
                    new ClassA() { classBObjects = new List<ClassB>() {
                       new ClassB() { Name = "X", Value = "1", IsVisible = true},
                       new ClassB() { Name = "Y", Value = "2", IsVisible = true},
                       new ClassB() { Name = "Z", Value = "3", IsVisible = true}
                    } },
                    new ClassA() { classBObjects = new List<ClassB> {
                       new ClassB { Name = "W", Value = "4", IsVisible = true},
                       new ClassB { Name = "X", Value = "1", IsVisible = true}
                    } }
                };
                List<Parameters> parameters = new List<Parameters>() { new Parameters() { Name = "X", Value = "1" }, new Parameters() { Name = "Y", Value = "2" } };
    
                var results = objects.SelectMany(x => x.classBObjects.Where(y => parameters.Any(z => y.Name == z.Name && y.Value == z.Value))).ToList();
            }
        }
        class ClassA {
           public ICollection<ClassB> classBObjects { get; set; }
        }
    
        class ClassB {
           public string Name { get; set; }
           public string Value { get; set; }
           public bool IsVisible { get; set; }
         }
        class Parameters
        {
            public string Name { get; set; }
            public string Value { get; set; }
        }