【问题标题】:Perform "except" using particular columns使用特定列执行“除外”
【发布时间】:2020-04-13 12:40:48
【问题描述】:

考虑 2 个具有相同架构的表:

var yesterday = new DataTable();
yesterday.Columns.Add("id", typeof(int));
yesterday.Columns.Add("part_number", typeof(string));
yesterday.Columns.Add("description", typeof(string));
yesterday.Columns.Add("comment", typeof(string));
yesterday.Columns.Add("information", typeof(string));
yesterday.Columns.Add("data", typeof(string));
var today = yesterday.Clone();

添加一些数据:

//yesterday data has 3 rows
yesterday.Rows.Add(1, "IVD_002", "IVD_002_RED", "Some comment", "Some information","Some data");
yesterday.Rows.Add(2, "IVD_003", "IVD_003_RED", "Some comment", "Some information", "Some data");
yesterday.Rows.Add(3, "IVD_004", "IVD_004_RED", "Some comment", "Some information", "Some data");

//today's data has the same 3 rows
today.Rows.Add(1, "IVD_002", "IVD_002_RED", "Some comment", "Some information", "Some data");
today.Rows.Add(2, "IVD_003", "IVD_003_RED", "Some comment", "Some information", "Some data");
today.Rows.Add(3, "IVD_004", "IVD_004_RED", "Some comment", "Some information", "Some data");

让我们添加更多数据:

//The New Row:
//In the output table I expect to see only the following row. The "id" column is 5 whereas in previous records there is no row with id = 5, part_number = IVD_002, description = IVD_002_RED
today.Rows.Add(5, "IVD_002", "IVD_002_RED", "Some comment", "Some information", "Some data");

//Another New Row:
//I dont expect to see this row in the result table because we are doing except only on "id","part_number","description" columns
today.Rows.Add(1, "IVD_002", "IVD_002_RED", "ROSES ARE RED", "PEANUTS", "=)");

我的目标是从“today”表中获取行,但“yesterday”表中的行除外,但只比较“id”、“part_number”、“description”列。

非常感谢纯 LINQ 解决方案,无循环。

【问题讨论】:

  • 你需要这样的东西stackoverflow.com/a/52738791/2946329
  • 这个解决方案打算在 UiPath 中使用,所以我可以用代码做的事情相当有限。 LINQ 查询将是最佳选择。
  • @SalahAkbari 这是一个数据表,而不是自定义对象的列表。
  • @juharr 我相信它仍然是可能的,也许像 IEqualityComparer<DataRow> 这样的东西,因为 DataTable 仍然是一个对象。

标签: c# linq datatable except


【解决方案1】:

我建议使用!Any。我更愿意将 yesterday DataTable 转换为 HashSet,这样您就不会经常线性扫描 yesterday 以查找匹配项,但我不确定 UiPath 有什么限制。

var result = today.AsEnumerable().Where(t => !yesterday.AsEnumerable().Any(y => (y["id"].Equals(t["id"]) &&
                                                                                 y["part_number"].Equals(t["part_number"]) &&
                                                                                 y["description"].Equals(t["description"]))))
                                 .CopyToDataTable();

如果你可以使用HashSet,那么你可以这样做:

var yesterdayHash = yesterday.AsEnumerable().Select(y => new { id = (int)y["id"], partnum = y["part_number"].ToString(), desc = y["description"].ToString() }).ToHashSet();
var result2 = today.AsEnumerable().Where(t => !yesterdayHash.Contains(new { id = (int)t["id"], partnum = t["part_number"].ToString(), desc = t["description"].ToString() })).CopyToDataTable();

使用用于创建返回匿名类型的Func 委托的静态类 Cast 助手,您可以创建一个 lambda 变量来保存公共键表达式:

public static class To {
    public static Func<TResult> Func<TResult>(Func<TResult> func) => func;
    public static Func<T, TResult> Func<T, TResult>(Func<T, TResult> func) => func;
}

var selectorFn = To.Func((DataRow r) => new { id = r.Field<int>("id"), partnum = r.Field<string>("part_number"), desc = r.Field<string>("description") });
var yesterdayHash2 = yesterday.AsEnumerable().Select(selectorFn).ToHashSet();
var result3 = today.AsEnumerable().Where(t => !yesterdayHash2.Contains(selectorFn(t))).CopyToDataTable();

注意:我更喜欢在DataRow 上使用Field&lt;&gt; 扩展方法来获取强类型DataTable 列值。

【讨论】:

  • 这才是真正的法师。非常感谢!
  • 至于 UiPath 中的 HashSet,不幸的是它不适用于匿名类型,所以我不得不坚持选项 #1
【解决方案2】:

我想我明白了。还有更好的方法吗?

var result = today.AsEnumerable()
    .Where(r =>
        today.AsEnumerable()
            .Select(r =>
                new { id = r["id"].ToString(), part_number = r["part_number"].ToString(), description = r["description"].ToString() })
            .Except(yesterday.AsEnumerable()
                .Select(r =>
                    new { id = r["id"].ToString(), part_number = r["part_number"].ToString(), description = r["description"].ToString() }))
            .Contains(new { id = r["id"].ToString(), part_number = r["part_number"].ToString(), description = r["description"].ToString() }))
    .CopyToDataTable();

【讨论】:

  • 有没有办法将“新”构造分配给变量?一个列数组或smth?所以我不会输入相同的 3 次
  • 如果你可以创建一个 lambda 变量,你可以做var selectorFn = (DataTable r) =&gt; new { id = r["id"].ToString(), part_number = r["part_number"].ToString(), description = r["description"].ToString() }; 然后使用它today.AsEnumerable().Where(r =&gt; today.AsEnumerable().Select(selectorFn).Except(yesterday.AsEnumerable().Select(selectorFn)).Contains(selectorFn(r)).CopyToDataTable();
  • 您需要一个静态转换助手才能使其工作。请参阅我的答案更新。
猜你喜欢
  • 2021-05-12
  • 2020-08-21
  • 1970-01-01
  • 2016-07-17
  • 2014-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多