先介绍一个例子,如下两个表格数据:
左边表格
|
右边表格
|
需求说明如下:
- 【分类】 列的值 有三种类型 0、1、2
- ItemCode是唯一的,在表格中不会重复
- 将左边的数据,按照ItemCode,复制到右边表格
如果左右两边都有ItemCode,检查分类值,如果对应的分类值是0:则不复制。
如果分类值是1:则修改属性值1
如果分类值是2:则修改属性值 1- 3
如果仅在右边的表格没有的数据,则把分类值修改成0,属性值不修改
如果仅在左边的表格有的数据,则右边新增一条,分类值是2,属性值1-3复制过来
如上例子,复制后的结果如下:
ItemCode | 分类 | 属性值1 | 属性值2 | 属性值3 |
A(不变) | 0 | 90 | 90 | 90 |
B(修改1) | 1 | 30 | 90 | 90 |
D(修改1-3) | 2 | 34 | 30 | 60 |
E(仅右边) | 0 | 90 | 90 | 90 |
C(仅左边) | 2 | 25 | 30 | 44 |
针对上述要求,程序实现可能如下:
public void CopyData(DataTable leftTable, DataTable rightTable) { //找出在仅在右边存在或者都存在的数据 foreach (DataRow row in rightTable.Rows) { string itemCode = row["ItemCode"] as string; DataRow[] rows = leftTable.Select("ItemCode = '" + itemCode + "'"); if (rows.Length == 0) { //仅修改分类值 row["分类"] = 0; } else { //左右都存在 int type = (int)row["分类"]; if (type > 0) { row["属性值1"] = row["属性值1"]; if (type == 2) { row["属性值2"] = row["属性值2"]; row["属性值3"] = row["属性值3"]; } } } } //找出仅在左边存在数据 foreach (DataRow row in leftTable.Rows) { string itemCode = row["ItemCode"] as string; DataRow[] rows = rightTable.Select("ItemCode = '" + itemCode + "'"); if (rows.Length == 0) { //在右边新增一条 DataRow newRow = rightTable.NewRow(); newRow["分类"] = 2; //分类值是2 newRow["ItemCode"] = itemCode; newRow["属性值1"] = row["属性值1"]; newRow["属性值2"] = row["属性值2"]; newRow["属性值3"] = row["属性值3"]; rightTable.Rows.Add(newRow); } else { //左右都存在 //什么也不做 } } }
上面的代码应该算是清晰的。而且有了足够的注释,不算很难读懂。
但是,这个代码和业务逻辑描述的是一致的吗?换句话说:如果没有需求文档,单单看上面代码,能得到上面的总结的需求吗?
我觉得要整理出上面需求比较难,理由如下。
- 从代码中看不出来分类值 是 仅有0、1、2
- 如果没有这些注释,也很难读出需求三种描述的逻辑(两者都有、仅左边有、仅右边有)。尤其两个循环存在,让代码逻辑更加不清晰。
针对第一个问题,似乎很好解决。可以定义一个枚举类型来描述分类值就可以了,如下:
public enum Enu分类 { 不改变 = 0, 仅改变1 = 1, 全部改变 = 2 }
相应的代码可以修改成这样
if (rows.Length == 0) { //仅修改分类值 row["分类"] =(int) Enu分类.不改变; } else { //左右都存在 Enu分类 type = (Enu分类)row["分类"]; switch(type) { case Enu分类.全部改变: row["属性值1"] = row["属性值1"]; row["属性值2"] = row["属性值2"]; row["属性值3"] = row["属性值3"]; break; case Enu分类.仅改变1: row["属性值1"] = row["属性值1"]; break; case Enu分类.不改变: break; } }
但是,对于第2个问题,则比较复杂了。如果和能像需求说明那样简洁的实现呢?
仔细分析,实际上需求说明中表述两个两层意思:
1、对比左右两个表格,得到都存在的、仅左边、仅右边结果2、三种数据处理方式如下:1)、都存在的处理方式按照分类2)、仅左边的,则在右边新增一行3)、仅右边的,则修改分类为0
那么另外一种实现方式是
1、做一个DataTable对比类,得到三种类型数据
2、遍历这三种数据即可。
实现方式如下:
public void CopyData2(DataTable leftTable, DataTable rightTable) { DataTableComparer cmp = new DataTableComparer(leftTable, rightTable, "ItemCode"); foreach(CompareResult result in cmp.Rows) { switch (result.Type) { case ResultType.Both: //左右都存在 Enu分类 type = (Enu分类)result.RightRow["分类"]; switch (type) { case Enu分类.全部改变: result.RightRow["属性值1"] = result.LeftRow["属性值1"]; result.RightRow["属性值2"] = result.LeftRow["属性值2"]; result.RightRow["属性值3"] = result.LeftRow["属性值3"]; break; case Enu分类.仅改变1: result.RightRow["属性值1"] = result.LeftRow["属性值1"]; break; case Enu分类.不改变: break; } break; case ResultType.LeftOnly: DataRow newRow = rightTable.NewRow(); newRow["分类"] = Enu分类.全部改变; newRow["ItemCode"] = result.LeftRow["ItemCode"]; newRow["属性值1"] = result.LeftRow["属性值1"]; newRow["属性值2"] = result.LeftRow["属性值2"]; newRow["属性值3"] = result.LeftRow["属性值3"]; rightTable.Rows.Add(newRow); break; case ResultType.RightOnly: //仅修改分类值 result.RightRow["分类"] = (int)Enu分类.不改变; break; } } }
TableComparer类实现如下:
class DataTableComparer { public DataTableComparer(DataTable left, DataTable right, params string[] keys) { m_rows = CreateAllRows(left.Rows, right.Rows, keys); } private string GetKey(DataRow row, string[] keys) { StringBuilder sb = new StringBuilder(); foreach (string key in keys) { sb.Append(Convert.ToString(row[key]) + "|"); } return sb.ToString(); } private Dictionary<string, CompareResult> CreateAllRows(IEnumerable leftRows, IEnumerable rightRows, string[] keys) { Dictionary<string, CompareResult> rows = new Dictionary<string, CompareResult>(); foreach (DataRow row in leftRows) { string key = GetKey(row, keys); CompareResult result = new CompareResult(); result.LeftRow = row; result.Type = ResultType.LeftOnly; rows.Add(key, result); } foreach (DataRow row in rightRows) { string key = GetKey(row, keys); if (rows.ContainsKey(key)) { CompareResult result = rows[key]; result.Type = ResultType.Both; result.RightRow = row; } else { CompareResult result = new CompareResult(); result.RightRow = row; result.Type = ResultType.RightOnly; rows.Add(key, result); } } return rows; } private Dictionary<string, CompareResult> m_rows; public IEnumerable<CompareResult> Rows { get { foreach (KeyValuePair<string, CompareResult> keyValue in m_rows) { yield return keyValue.Value; } } } } public class CompareResult { public ResultType Type; public DataRow LeftRow; public DataRow RightRow; } public enum ResultType { Both, LeftOnly, RightOnly, }
结论:
- 软件代码中的注释对代码维护工作非常重要。但是,结构良好,更接近于自然语言描述的代码更重要。甚至,有时候可以替代注释的效果。
- 软件需求在转换为软件代码过程中,仍然有值得仔细推敲,整理的必要,这样才能保证在实现过程中,尽量与软件需求描述一致。
- 当然,上述代码中,第二种方案有时候也未必是最优的。如果考虑性能的话,大数据量的dataTable,可能第一种方案还是可取的。
- 以上没有使用Linq去实现,如果用到Linq的话,代码应该还可以简化。