使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象
程序员文章站
2022-05-12 12:58:32
使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象 ......
本文需要对c#里的linq、lambda 表达式 、委托有一定了解。
在工作中,经常遇到需要对比两个集合的场景,如:
- 页面集合数据修改,需要保存到数据库
- 全量同步上游数据到本系统数据库
在这些场景中,需要识别出需要新增、更新、删除的数据,由于每次应用是,需要比较的对象类型不一致,因此写了个相对通用的方法。这个过程中,需要理解的有以下2个核心概念:
- 唯一标识比较: 如果两个对象的唯一标识相等,则认为这两个对象在业务上代表同一个东西(次要属性是否相等暂不考虑)。
- 实体比较:表示两个对象在业务是不是相等(唯一标识相等、次要属性相等)。
代码示例如下:
void main() { // 对比源集合 var source = generatestudent(1, 10000, 1000); // 目标集合 var target = generatestudent(5000, 10000, 1000); // 唯一标识比较 func<student, student, bool> keycompartor = (s, t) => s.id == t.id; // 实体相等比较 func<student, student, bool> entitycompartor = (s, t) => s.id == t.id && s.name.equals(t.name) && s.age == t.age; // 新增前准备 func<student, student> insertaction = (s) => { return new student { id = s.id, name = s.name, age = s.age, operation = "insert" }; }; // 更新前准备 func<student, student, student> updateaction = (s, t) => { t.name = s.name; t.age = s.age; t.operation = "update"; return t; }; // 删除前准备 func<student, student> deleteaction = (t) => { t.operation = "delete"; return t; }; // 去掉相等对象 removeduplicate(source, target, entitycompartor, (s1, s2) => s1.id == s2.id, keycompartor); // 需要新增的集合 var insertingstudents = getinsertingentities(source, target, keycompartor, insertaction); // 需要更新的集合 var updatingstudents = getupdatingentities(source, target, keycompartor, entitycompartor, updateaction); // 需要删除的集合 var deletingstudents = getdeletingentities(source, target, keycompartor, deleteaction); // 后续业务 // insertstudents(insertingstudents); // updatestudents(updatingstudents); // deletestudents(deletingstudents); } // 集合去重 private void removeduplicate<s, t>(list<s> source, list<t> target, func<s, t, bool> entitycompartor, func<s, s, bool> sourcekeycompartor, func<s, t, bool> keycomportor) { var sameentities = source.where(s => target.exists(t => entitycompartor(s, t))).tolist(); source.removeall(s => sameentities.exists(s2 => sourcekeycompartor(s, s2))); target.removeall(t => sameentities.exists(s => keycomportor(s, t))); } // 获取需要新增的对象集合 private list<t> getinsertingentities<s, t>(list<s> source, list<t> target, func<s, t, bool> keycomportor, func<s, t> insertaction) { var result = new list<t>(); foreach (var s in source) { var t = target.firstordefault(x => keycomportor(s, x)); if (t == null) { // 目标集合中不存在,则新增 result.add(insertaction(s)); } } return result; } // 获取需要更新的对象集合 private list<t> getupdatingentities<s, t>(list<s> source, list<t> target, func<s, t, bool> keycomportor, func<s, t, bool> entitycompartor, func<s, t, t> updateaction) { var result = new list<t>(); foreach (var s in source) { var t = target.firstordefault(x => keycomportor(s, x)); if (t != null && !entitycompartor(s, t)) { // 目标集合中存在,但是次要属性不相等,则更新 result.add(updateaction(s, t)); } } return result; } // 获取需要删除的对象集合 private list<t> getdeletingentities<s, t>(list<s> source, list<t> target, func<s, t, bool> keycomportor, func<t, t> deleteaction) { var result = new list<t>(); foreach (var t in target) { var s = source.firstordefault(x => keycomportor(x, t)); if (s == null) { // 源集合中存在,目标集合中需要删除 result.add(deleteaction(t)); } } return result; } // 随机生成测试集合 private list<student> generatestudent(int minid, int maxid, int maxnumber) { var r = new random(); var students = new list<student>(); for (int i = 0; i < maxnumber; i++) { students.add(new student { id = r.next(minid, maxid), name = $"name: {r.next(1, 10)}", age = r.next(6, 10) }); } return students.groupby(s => s.id).select(s => s.first()).tolist(); } public class student { public int id { get; set; } public string name { get; set; } public int age { get; set; } public string operation { get; set; } }
例子中源集合与目标集合使用了相同的对象student
,但实际使用中,两者的类型可以不一样,只要最终返回目标集合的类型就可以了。
上面是我对集合比较的一点心得,只满足了小数据量的业务情景,并没有在大数据量的情况下做过调优。在这里也算是抛砖引玉,大家要是有更好的办法,还希望不吝赐教。