手邊有個系統包含轄區概念,每隔一陣子就會微調,把主管A的管區1移給主管B,主管B的管區2移給主管C... 玩一場大風吹。由於提供資料直接交換管道,我們只能依使用者提供的文字檔更新資料庫(實際上使用者提供的檔案格式沒標準化,有時是Excel、有時是PDF,順便大推Word 2013直接開啟PDF編輯的功能,很神!)。因此我的做法是將資料庫現有設定與使用者提供的新設定都轉成格式一致的文字,再設法比對二者差異產生DELETE、UPDATE、INSERT指令(全部刪除再新增是種簡便做法,但缺點時無法產生異動報表供使用者確認)。過去我多半會寫出兩個迴圈,巡一次新資料,找出要變更或新增的項目,再巡一次舊資料,找出要刪除的項目,這回嘗試用LINQ的Except比對,程式異常簡潔,特筆記分享:

來胡搞一個範例,假設三國時代原本統治範圍如下:

曹操:冀州、兗州、青州、徐州、梁州、雍州、豫州
孫權:揚州
劉備:益州、荊州
黑大:潮州
梅西:美州

經過一番爭戰後變更如下:

曹操:冀州、兗州、青州、潮州、梁州、雍州、豫州
孫權:揚州、荊州、交州
劉備:益州
黑大:徐州
魯本:美州

程式如下。先將設定解析成Dictionary<string, List<string>>,接著利用Union()取得新舊設定七位領袖清單,逐一檢查其在新舊設定的轄區集合(若無轄區資料則視為空集合),透過新轄區集合.Except(舊轄區集合)即為待新增項目,而用舊轄區集合.Except(新轄區集合)則可取得待刪除項目,就能輕鬆產生DELETE與INSERT指令囉~ (註: 在本案例假設資料來自內部並經人工檢查,無SQL Injection風險,故採SQL字串組裝以求簡便。在輸入資料來源無法100%掌握的情境裡,絕對不可這麼做。再補充一次SQL Injection安全宣導)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
                    
public class Program
{
    static Dictionary<string, List<string>> parse(string input)
    {
        var res = new Dictionary<string, List<string>>();
        foreach (string line in input.Replace("\r", "").Split('\n'))
        {
            var a1 = line.Split(':');
            var a2 = a1.Last().Split('、');
            res.Add(a1.First(), new List<string>(a2));
        }
        return res;
    }    
    
    public static void Main()
    {
        string srcOrig = 
@"曹操:冀州、兗州、青州、徐州、梁州、雍州、豫州
孫權:揚州
劉備:益州、荊州
黑大:潮州
梅西:美州";
        string srcNew = 
@"曹操:冀州、兗州、青州、潮州、梁州、雍州、豫州
孫權:揚州、荊州、交州
劉備:益州
黑大:徐州
魯本:美州";
        //解析轉為Dictionary<string, List<string>>資料結構
        var origTerritory = parse(srcOrig);
        var newTerritory = parse(srcNew);    
        
        StringBuilder sb = new StringBuilder();
        //利用Union結合新舊設定所有領袖
        foreach (string leader in origTerritory.Keys.Union(newTerritory.Keys))
        {
            //取得轄區清單,無資料時取空集合
            var origList = origTerritory.ContainsKey(leader) ? 
                            origTerritory[leader] : new List<string>();
            var newList = newTerritory.ContainsKey(leader) ? 
                            newTerritory[leader] : new List<string>();
            //從新清單剔除舊清單已有項目,即為待增加項目
            var toAdd = newList.Except(origList);
            //從舊清單剔除新清單已有項目,即為待刪除項目
            var toDel = origList.Except(newList);
            //產生註解
            sb.AppendFormat("--{0} 刪:{1} 增:{2}\n", leader,
                string.Join(",", toDel.ToArray()), 
                string.Join(",", toAdd.ToArray()));
            //【資安提醒】
            //  為求簡化,本例假設資料源自內部系統,已排除SQL Injection風險,
            //  若無法確認資料可靠性時,請勿使用外部輸入資料組裝SQL指令
            foreach (var d in toDel) 
            {
                sb.AppendFormat(
                    "DELETE FROM Territory WHERE Leader='{0}' AND State='{1}'\n",
                    leader, d
                );
            }
            foreach (var a in toAdd) 
            {
                sb.AppendFormat(
                    "INSERT INTO Territory VALUES ('{0}','{1}')\n",
                    leader, a
                );
            }
        }
        Console.Write(sb.ToString());
    }
}

執行結果如下:

--曹操 刪:徐州 增:潮州
DELETE FROM Territory WHERE Leader='曹操' AND State='徐州'
INSERT INTO Territory VALUES ('曹操','潮州')
--孫權 刪: 增:荊州,交州
INSERT INTO Territory VALUES ('孫權','荊州')
INSERT INTO Territory VALUES ('孫權','交州')
--劉備 刪:荊州 增:
DELETE FROM Territory WHERE Leader='劉備' AND State='荊州'
--黑大 刪:潮州 增:徐州
DELETE FROM Territory WHERE Leader='黑大' AND State='潮州'
INSERT INTO Territory VALUES ('黑大','徐州')
--梅西 刪:美州 增:
DELETE FROM Territory WHERE Leader='梅西' AND State='美州'
--魯本 刪: 增:美州
INSERT INTO Territory VALUES ('魯本','美州') 

Online Demo (順便推薦好用的.NET Fiddle)


Comments

# by sss

ss

Post a comment