Home > Software engineering >  C# LINQ query Select projection, Function programming
C# LINQ query Select projection, Function programming

Time:12-21

public class Factory
{
    public string Number { get; set; }
    public List<OrganizationUnit> Units;
}

public class OrganizationUnit
{
    public string Name { get; set; }
    public string Id { get; set; }
    public bool ?IsActive { get; set; }
}

public static  class LocalisationRepo  
{
    public static List<Factory> GetFactories()
    {
        List<Factory> fa = new List<Factory>();
        Factory factory = new Factory() { Number="F10", Units = new List<OrganizationUnit> { 
            new OrganizationUnit() 
            { Id = "001", Name = "Quality", IsActive = false}, 
            new OrganizationUnit() { Id = "002", Name = "Line 3", IsActive=null } ,
            new OrganizationUnit { Id="003", Name="IT", IsActive=true } } };

                  Factory factory2 = new Factory()
            {
                Number = "F11",
                Units = new List<OrganizationUnit> {
                new OrganizationUnit()
                { Id = "001", Name = "Quality", IsActive = true},
                new OrganizationUnit() { Id = "002", Name = "Line 3", IsActive=true } ,
                new OrganizationUnit { Id="003", Name="IT", IsActive=true } }
            };

            fa.Add(factory);
            fa.Add(factory2);
        
        return fa;
    }

}

There is a linq Query

var factories = LocalisationRepo.GetFactories();

var fa = factories.SelectMany(f => f.Units.Where(u => (u.IsActive ?? false) == false), (f, u) => f).Distinct();

foreach (var item in fa)
{
    Console.WriteLine(item.Number);
}

The first part f => f.Units.Where(u => (u.IsActive ?? false) == false)
gives us IEnumerable<OrganizationUnit> the second parameter (f, u) => f (A transform function to apply to each element of the intermediate sequence) "projects each element of sequence to IEnumerable<Out T>"

My question is how transition from IEnumerable<OrganizationUnit> to IEnumerable<Factory> is made by this selector/delegate when at "output" from first argument is IEnumerable<OrganizationUnit> How should it be considered / understood?

We know that f is a Factory, but "intermediate result" is OrganizationUnit so ... HOW ? Some theory about function programming?

I want factories with inactive OrganisationUnits IsActive=false; But I'm not asking about how to fint result because my example works fine. I would like to know how it works, and why...

The same as in Microsoft example https://docs.microsoft.com/en-US/dotnet/api/system.linq.enumerable.selectmany?view=net-6.0

We can make query var query = petOwners .SelectMany(po => po.Pets.Where(p => p.StartsWith("S")), (po, p) => po);

CodePudding user response:

SelectMany operator used to select the elements from collection of collection called Nested Collection.

SelectMany returns a single result from a nested collection You can use SelectMany to get every OrganizationUnit line from the factory.

var fa = factories.SelectMany(f => f.Units.Where(u => !(u.IsActive??false)), (factory, unit) => unit);

You are trying to in the reverse direction.

Important do not use .Distinct() to fix the duplicates. The duplicates should not be there in the first place.

Solution:

var fa = factories.Where( factory => factory.Units.Any(unit => (unit.IsActive?? false) == false));

CodePudding user response:

I think the confusion here is related to the (unnecessary) use of SelectMany to filter the factories.

In this statement:

var fa = factories.SelectMany(f => f.Units.Where(u => (u.IsActive ?? false) == false), (f, u) => f);

The (f, u) => f parameter is basically ignoring what selectmany picks (which is u), and picking the original factory f anyways.

So in the end, your statement is equivalent to: ``` var fa = factories.Select(f => f); ``` Which is basically a no-op.

So the above is not exactly the case. It turns out, that your actual query is equivalent to

var fa = factories.Where(f => f.Units.Any(u => (u.IsActive ?? false) == false);

This is because, for every factory, that SelectMany overload will pick each of the matching Units you provided, hand it to you as the func parameters on the third argument, and wait for you to then transform it to whatever you want. So it expects you to transform a (Factory,Unit) pair into some T. You decided to avoid the transformation and instead pick the first element, the Factory f. Then, SelectMany will combine the result of all such lambda executions into a final sequence, in your case, containing factories only. If any factory in your list contains more than one disabled unit, it will appear multiple times in the final result, which is why you had to add a Distinct to it for the query "to make sense".

If you want to filter for "factories with at least one inactive unit", you'd do something like this instead:

var fa = factories.Where(f => f.Units.Any(u => (u.IsActive ?? false) == false);

And if you want "factories whose units are all inactive", you'd do this:

var fa = factories.Where(f => f.Units.All(u => (u.IsActive ?? false) == false);

We know that f is a Factory, but "intermediate result" is OrganizationUnit so ... HOW ? Some theory about function programming?

The "intermediate result" is not OrganizationUnit: with this overload, the intermediate result is actualy a (Factory,OrganizationResult) pair, which you are "transforming" to just Factory by picking f as the result. There is nothing special about it.

SelectMany should be used for flattening results, which you don't seem to need.

CodePudding user response:

How should it be considered / understood?

You're doing a SelectMany on a list of Factories and you're telling SM you want it to flatten the Units in each Factory. The second argument represents a function that takes both the current Factory being iterated and one of the Units being iterated.

listOfFactories.SelectMany(aFactory => aFactory.listOfTheUnits, (theCurrentFactory, oneOfTheUnitsOfTheCurrentFactory) => ...)

Or in non delegate terms

listOfFactories.SelectMany(GetUnitsFromAFactory, DoSomethingWithAFactoryAndUnitAndGetAnOutput)

A SelectMany is conceptually just a nested pair of foreach loops; in those terms it might be like:

var output = List<SomeOutput>();

foreach(var aFactory in listOfFactories){
  var theCurrentFactory = aFactory;
  var someEnumerable = GetUnitsFromAFactory();

  foreach(var oneOfTheUnitsOfTheCurrentFactory in someEnumerable){
    output.Add(DoSomethingWithAFactoryAndUnitAndGetAnOutput(theCurrentFactory, oneOfTheUnitsOfTheCurrentFactory);
  }
}

//methods 
IEnumerable<OrganizationUnit> GetUnitsFromAFactory(Factory f){
  return f.listOfTheUnits;
}

SomeOutput DoSomethingWithAFactoryAndUnitAndGetAnOutput(Factory f, Unit u) {
  ...
}

The confusing thing with your code is that you only return the factory from the "DoSomethingWithAFactoryAndUnitAndGetAnOutput", so if you had 3 factories with 10, 11 and 12 units respectively you'd end up with a list of 33 factories, 10 repeats of the first factory, 11 repeats of the second and 12 repeats of the third. Now we're (hopefully) happy that SelectMany is a nested loop pair, let's add in the other bit about the units being inactive

var out = new List<Factory>
foreach(var f in factories)
  foreach(var u in f.Units)
    if(unit.IsActive ?? false == false)
      out.Add(f);

You see, you've added a factory multiple times, once for every inactive unit it has

You then Distinct these to collapse them so the whole SelectMany was something of a wasted exercise and you could instead just ask for the factories with inactive units. In simple loop terms that might look like:

var out = new List<Factory>
foreach(var f in factories)
  foreach(var u in f.Units)
    if(unit.IsActive ?? false == false){
      out.Add(f);
      break;
    }

Adding the break in stops after adding a single factory (encountering the first inactive unit). In progressively-adding-LINQ terms that's more like doing

foreach(var f in factories)
  if(f.Units.Any(unit => unit.IsActive ?? false == false))
    out.Add(f);
     

Which is

factories.Where(f => f.Units.Any(unit => unit.IsActive ?? false == false))
  • Related