2

I have a special issue I want to solve by using LINQ but my solution does not work yet. I condensed the structure to the minimum and hope it is easy to understand. The classes are as follows:

public class A
{
    public List<B> ListOfBs { get; set; }
}

public class B
{
    public List<C> ListOfCs { get; set; }
}

public class C
{
    public int ID { get; set; }
    public string SomeText { get; set; }
}

In my code I want to filter for specific C's and set them into a new List as property of a new instance of B (or even A). So I tried the following Linq query within a function:

    public B FilterFunction(A a, string someText)
    {
        B FilteredB = new B();
        FilteredB.ListOfCs = (from b in a.ListOfBs
                              from c in b.ListOfCs
                              where c.SomeText == someText
                              select c).ToList();
        return FilteredB;
    }

however, the returned list is empty, so I guess my LINQ query is not correct. Any suggestions? Thank you in advance Martin

2
  • To be clear, you have an A that contains a List<B> and each B contains a List<C>, and you want to create and return a single new B that contains a List<C> where each C matches a specific condition? If so then that is a job for SelectMany. I'll put together an answer to that effect. Commented May 10, 2021 at 5:56
  • Your implementation is correct. Your problem should be on data Commented May 10, 2021 at 6:18

6 Answers 6

1

So you have an object a of type A. This a has zero or more Bs, where every B has zero or more Cs. Every C has a String property SomeText.

You also have a String someText.

I want to filter for specific C's and set them into a new List as property of a new instance of B.

This is a bit ambiguous. What do you want:

  • I want all Cs with a value for Property SomeText that equals someText, that I can get via my a in one B. Literally your requirement says that you want one new Instance of B
  • No, I want to create from Every B that I can get via a, one new B. This new B has all Cs that the original B had, with a value for Property SomeText that equals someText.

Create one new instance of B

Whenever you have a sequence of items, where every item has a subsequence, and you want to regard these subsequences as one big sequence, consider to use one of the overloads of Enumerable.SelectMany.

From all Cs that are in all Bs that are in your a, you want to make one new B. Well, not exactly all Cs, only those Cs that have the correct value for property SomeText.

A a = ...
string someText = ...

var oneNewB = new B
{
    ListOfCs = a.ListOfBs.SelectMany(b => b.ListOfCs)
        .Where(c => c.SomeText == someText)
        .ToList(),
};

In words: from a, get its list of Bs. From every B, use the subsequence ListOfCs to make one big sequence of Cs. Keep only those Cs that have a value for property SomeText that equals the value of someText. Use the remaining Cs to use as ListOfCs in one new B.

Make one new B from every B in a

From every B in a.ListOfBs we make one new B. This new B contains only the Cs in the B that have a matching SomeText.

var newBs = a.ListOfBs.Select(b => new B
{
    ListOfCs = b.ListOfCs.Where(c => c.SomeText == someText).ToList(),
});

In words: From every B in a, make one new B. This new B contains only the Cs from the original B with the correct value for property SomeText.

Sign up to request clarification or add additional context in comments.

Comments

1

Your code may not be optimized but it works. Did you populate your lists corectly?

B lb = new B();
lb.ListOfCs = new List<C>();
lb.ListOfCs.Add(new C { ID = 1, SomeText = "1" });
lb.ListOfCs.Add(new C { ID = 2, SomeText = "2" });
lb.ListOfCs.Add(new C { ID = 3, SomeText = "3" });
       
B lb2 = new B();
lb2.ListOfCs = new List<C>();
lb2.ListOfCs.Add(new C { ID = 10, SomeText = "10" });
lb2.ListOfCs.Add(new C { ID = 20, SomeText = "20" });
lb2.ListOfCs.Add(new C { ID = 30, SomeText = "10" });

A la = new A();
la.ListOfBs = new List<B>();
la.ListOfBs.Add(lb2);
la.ListOfBs.Add(lb);

FilterFunction(la, "10").ListOfCs.ForEach(x => Console.WriteLine(x.ID));

Result 10, 30

Comments

1

You may use .SelectMany() to flatten ListOfCs for easing the query of the C element.

.SelectMany()

Projects each element of a sequence to an IEnumerable and flattens the resulting sequences into one sequence.

public B FilterFunction(A a, string someText)
{
    B FilteredB = new B();
    FilteredB.ListOfCs = a.ListOfBs
        .SelectMany(b => b.ListOfCs)
        .Where(c => c.SomeText == someText)
        .ToList();
    
    return FilteredB;
}

Sample code & test data

Comments

1

Using SelectMany as suggested:

class Program
{
    static void Main()
    {
        var a = new A
                {
                    ListOfBs = new List<B>
                               {
                                   new B
                                   {
                                       ListOfCs = new List<C>
                                                  {
                                                      new C {ID = 1, SomeText = "One"},
                                                      new C {ID = 2, SomeText = "Two"}
                                                  }
                                   },
                                   new B
                                   {
                                       ListOfCs = new List<C>
                                                  {
                                                      new C {ID = 3, SomeText = "Three"},
                                                      new C {ID = 4, SomeText = "Four"}
                                                  }
                                   }
                               }
                };

        var b = FilterFunction(a, "T");

        foreach (var c in b.ListOfCs)
        {
            Console.WriteLine($"{c.ID}: {c.SomeText}");
        }
    }

    public static B FilterFunction(A a, string someText)
    {
        return new B
               {
                   ListOfCs = a.ListOfBs
                               .SelectMany(b => b.ListOfCs.Where(c => c.SomeText.StartsWith(someText)))
                               .ToList()
               };
    }
}

public class A
{
    public List<B> ListOfBs { get; set; }
}

public class B
{
    public List<C> ListOfCs { get; set; }
}

public class C
{
    public int ID { get; set; }
    public string SomeText { get; set; }
}

1 Comment

Wow! I forgot how much quicker C# questions get answered compared to VB.NET questions.
0

With .SelectMany you can flatten all C lists into one and filter it

public B FilterFunction(A a, string someText)
{
    return new B
    {
        ListOfCs = a.ListOfBs
            .SelectMany(bc => bc.ListOfCs)
            .Where(c => c.SomeText == someText)
            .ToList()
    }
}

Comments

-1

try this

public B FilterFunction(A a, string someText)
{
    B FilteredB = new B();
    FilteredB.ListOfCs = a.ListOfCs.Where(c=> c.SomeText.Equals(someText, StringComparison.InvariantCultureIgnoreCase)).ToList();
    return FilteredB;
}

2 Comments

A doesn't has a ListOfC Property
Oops, I did not see that, check @Fabio's answer

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.