C# LINQ查询Select投影,函数编程

C# LINQ query Select projection, Function programming

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;
    }

}

有一个linq查询

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);
}

第一部分f => f.Units.Where(u => (u.IsActive ?? false) == false)
给我们 IEnumerable<OrganizationUnit> 第二个参数 (f, u) => f(应用于中间序列的每个元素的转换函数) “将序列的每个元素投影到 IEnumerable<Out T>

我的问题是,当第一个参数的“输出”是 IEnumerable<OrganizationUnit> 时,这个 selector/delegate 如何实现从 IEnumerable<OrganizationUnit>IEnumerable<Factory> 的转换 应该如何看待/理解?

我们知道 f 是一个工厂,但是“中间结果”是 OrganizationUnit 所以...如何?关于函数编程的一些理论?

我想要具有不活动 OrganisationUnits 的工厂 IsActive=false; 但我不是在问如何找到结果,因为我的例子工作正常。 我想知道它是如何工作的,为什么...

与 Microsoft 示例相同 https://docs.microsoft.com/en-US/dotnet/api/system.linq.enumerable.selectmany?view=net-6.0

我们可以查询 变种查询= 宠物主人 .SelectMany(po => po.Pets.Where(p => p.StartsWith("S")), (po, p) => po);

SelectMany 运算符用于 select 来自名为 Nested Collection 的集合的元素。

SelectMany returns 来自嵌套集合的单个结果 您可以使用 SelectMany 从工厂获取每个 OrganizationUnit 行。

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

您正在尝试反方向。

重要 不要使用 .Distinct() 来修复重复项。一开始就不应该有重复项。

解法:

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

我认为这里的混淆与(不必要的)使用 SelectMany 来过滤工厂有关。

在此声明中:

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

(f, u) => f这个参数基本就是忽略了selectmany挑的东西(也就是u),反正都是挑原厂f

所以最后,你的陈述等同于: ``` var fa = factories.Select(f => f); ``` 这基本上是一个空操作。

所以上面的情况不完全是这样。事实证明,您的实际查询等同于

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

这是因为,对于每个工厂,SelectMany 重载将选择您提供的每个匹配的 Unit,将其作为第三个参数的 func 参数传递给您,然后等待然后您可以将其转换为您想要的任何东西。因此它希望您将 (Factory,Unit) 对转换为 some T。您决定避免转换,而是选择第一个元素 Factory f。然后,SelectMany 会将所有此类 lambda 执行的结果组合成最终序列,在您的情况下,仅包含工厂。如果您列表中的任何工厂包含多个禁用单元,它将在最终结果中出现多次,这就是为什么您必须向其添加 Distinct 以使查询“有意义”。

如果你想过滤“至少有一个非活跃单位的工厂”,你会做这样的事情:

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

如果你想要“所有单元都处于非活动状态的工厂”,你可以这样做:

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?

“中间结果”是不是 OrganizationUnit:有了这个重载,中间结果实际上是一对(Factory,OrganizationResult) 通过选择 f 作为结果“转换”为 Factory。没什么特别的。

SelectMany 应该用于扁平化结果,您似乎不需要。

How should it be considered / understood?

您正在对工厂列表执行 SelectMany 操作,并告诉 SM 您希望它压平每个工厂中的单位。第二个参数表示一个函数,它同时接受正在迭代的当前工厂和正在迭代的单元之一。

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

或在非代表条款中

listOfFactories.SelectMany(GetUnitsFromAFactory, DoSomethingWithAFactoryAndUnitAndGetAnOutput)

SelectMany 在概念上只是一对嵌套的 foreach 循环;用这些术语来说,它可能是这样的:

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) {
  ...
}

你的代码令人困惑的是你只 return 来自“DoSomethingWithAFactoryAndUnitAndGetAnOutput”的工厂,所以如果你有 3 个分别有 10、11 和 12 个单元的工厂,你最终会得到一个列表33 家工厂中,第一家工厂有 10 次重复,第二家工厂有 11 次重复,第三家工厂有 12 次重复。现在我们(希望)很高兴 SelectMany 是一个嵌套循环对,让我们添加关于单元处于非活动状态的其他位

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

你看,你已经多次添加工厂,每添加一个不活动的单元一次

然后您将它们分开以折叠它们,这样整个 SelectMany 就成了一种浪费的练习,您可以改为只请求具有不活动单元的工厂。在简单的循环术语中,可能看起来像:

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;
    }

在添加单个工厂后添加中断停止(遇到第一个不活动的单元)。在渐进式添加 LINQ 术语中,这更像是在做

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

这是

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

我不认为 SelectMany 正在做您认为正在做的事情。我想你认为它通过某种从单位集合返回工厂的投影将单位过滤到 IsActivefalse 的工厂返回工厂。这是不正确的。

SelectMany 的第二个选择器是 resultSelector,您在其中传递:

 (f, u) => f

其中 fu 是源集合的项目,以及前一个参数投影的集合的项目(您的 Where 结果)。这意味着对于满足 Where 条件 (u) 的任何单元,它 returns 包含它的工厂 (f).这就是您必须调用 Distinct 的原因,因为您实际上是在为每个匹配的单元 获取工厂 。如果一家工厂有两个符合标准的单位,那么您将两次返回同一家工厂。 Unique 从结果中删除那些重复的工厂,但工厂本身(意味着其中的单元连接)没有改变。

为了“过滤”子集合,您需要重新投影工厂:

var fa = factories.Select(f => new Factory(
        {
          Number = f.Number, 
          Units = f.Units.Where(u => (u.IsActive ?? false) == false).ToList();
        }
      )
    );

或者,循环遍历工厂并 删除 IsActivetrue 的单元。