C#lambda - 两个列表中内部列表的差异(除外)

时间:2016-01-19 23:49:08

标签: c# lambda

我有两个'Table'1 ---- N'列的列表。 第一个列表包含必须实现的默认架构。 第二个列表包含用户定义的模式。 我需要将第二个列表与第一个列表进行比较,检索架构不匹配的表,以及缺失/未知列的列表。

考虑以下示例:

public class Table
{
  public string Name {get;set;}
  public IList<Column> Columns {get;set;}
  public Table()
  {
    Columns = new List<Column>();
  }
}
public class Column
{
  public string Name {get;set;}
}
//...
var Default1 = new Table() { Name = "Table1" };
Default1.Columns.Add(new Column() { Name = "X1" });
Default1.Columns.Add(new Column() { Name = "X2" });
var Default2 = new Table() { Name = "Table2" };
Default2.Columns.Add(new Column() { Name = "Y1" });
Default2.Columns.Add(new Column() { Name = "Y2" });

var DefaultSchema = new List<Table>() { Default1, Default2 };

var T1 = new Table() { Name = "Table1" };
T1.Columns.Add(new Column() { Name = "X1" });
var T2 = new Table() { Name = "Table2" };
T2.Columns.Add(new Column() { Name = "Y2" });

var MyTables = new List<Table>() { T1, T2};

/*
var DiffTables = DefaultSchema.Join(??).Select(x => x.Columns).Except(?? MyTables.Select(y => y.Columns) ...
*/

预期结果:

var DiffTables =
 {
  {
    Name = "Table1",
    Columns =
    {
      Name = "X2" //Missing from DefaultSchema.Table1
    }
  },
  {
    Name = "Table2",
    Columns =
    {
      Name = "Y1" //Missing from DefaultSchema.Table2
    }
  }
 }

有没有办法用lamdba表达式执行此操作,或者只使用master +嵌套foreach?

谢谢!

3 个答案:

答案 0 :(得分:2)

仅比较两个表,它将是:

Default1.Columns
    .Select(x => x.Name)
    .Except(T1.Columns.Select(x => x.Name));

为了比较两个模式,它将是:

DefaultSchema
    .Zip(MyTables, (x, y) => new
        { Name = x.Name,
          MissingColumns = x.Columns.Select(x1 => x1.Name)
              .Except(y.Columns.Select(y1 => y1.Name)) });

Zip组合任意两个序列,因此第1项与第2项匹配,第2项与第2项匹配等(换句话说,就像拉链一样)。

Except从另一个序列中删除一个序列的所有项目。

正如@MetroSmurf指出的那样,我的原始版本出现了导致Except失败的错误。原因是它是根据列是否指向同一对象来比较列。我添加了内部Select语句,以便通过Name来比较列。

另请注意,此答案假定要比较的两个架构具有相同顺序的相同表。

另一种方法(灵感来自@ MetroSmurf使用IEquatable)是创建自定义IEqualityComparer,如下所示:

public class ColumnComparer : IEqualityComparer<Column>
{
    public bool Equals(Column x, Column y)
    {
        if (Object.ReferenceEquals(x, y)) return true;
        if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) return false;
        return x.Name == y.Name;
    }

    public int GetHashCode(Column column)
    {
        if (Object.ReferenceEquals(column, null)) return 0;
        return column.Name.GetHashCode();
    }
}

然后LINQ查询减少到这个:

DefaultSchema.Zip(MyTables, (x, y) => new
{
    Name = x.Name,
    MissingColumns = x.Columns.Except(y.Columns, new ColumnComparer())
});

同样,适用于两个模式的表相同的假设适用。

如果这个假设不适用(即MyTables没有按顺序或可能缺少表格),你可以使用“左外连接”代替:

var result =
    from table in DefaultSchema
    join myTable in MyTables on table.Name equals myTable.Name into matchingTables
    from matchingTable in matchingTables.DefaultIfEmpty()
    select new
    {
        Name = table.Name,
        MissingColumns = matchingTable == null
            ? null
            : table.Columns.Except(matchingTable.Columns, new ColumnComparer())
    };

使用此查询,将为DefaultSchema中的每个表生成结果。如果MyTables中的一个或多个具有相同的名称,则会报告缺少的列。如果MyTables中缺少该表,则MissingColumns的值为null。请注意,这不会报告MyTablesDefaultSchema中不存在的任何额外表。

答案 1 :(得分:1)

以下是如何做到这一点:

var result =
    DefaultSchema
        .Select(
            table =>
                new
                {
                    Table = table,
                    UserTable = MyTables.FirstOrDefault(utable => utable.Name == table.Name)
                })
        .Select(item => new
        {
            Name = item.Table.Name,
            MissingColumns =
                item.UserTable == null
                    ? item.Table.Columns.Select(x => x.Name).ToArray()
                    : item.Table.Columns.Select(x => x.Name)
                        .Except(item.UserTable.Columns.Select(x => x.Name))
                        .ToArray()
        }).ToList();

此代码处理两个列表不能保证具有相同数量的表或使表格顺序正确的情况。

首先选择默认架构表及其相应的用户表(或找不到用户表的null)。

然后,对于每个这样的对象,它创建一个新对象,其中包含默认的模式表名称和缺失列的列表。

如果找不到相应的用户表,则缺失列的列表是默认架构表中的所有列。

如果找到相应的用户表,Except用于从默认架构表中定义的列中减去用户表中定义的列列表。

答案 2 :(得分:1)

使用复杂的Linq查询会使更容易理解和可维护的循环过于复杂。 devuxer建议的替代方案(假设两个列表中的所有内容都可以压缩,效果很好):

首先,我实现IEquatable以便于比较:

public class Column : IEquatable<Column>
{
    public string Name { get; set; }

    public bool Equals( Column other )
    {
        // consider case insensitive comparison if needed.
        return Name == other.Name;
    }
}

然后循环变为:

var diffs = new List<Table>();

foreach( Table table in MyTables )
{
    Table schema = DefaultSchema
        // consider case insensitive comparison if needed.
        .FirstOrDefault( x => x.Name == table.Name );

    if( schema == null )
    {
        // no matching schema, everything should be evaluated.
        diffs.Add( table );
        continue;
    }

    // use IEquatable to pull out the differences
    List<Column> columns = table.Columns.Except( schema.Columns ).ToList();

    if( columns.Any() )
    {
        diffs.Add( new Table { Name = table.Name, Columns = columns } );
    }
}