記的在上一家公司作全內存項目的時候,由於一些關鍵表會在程序 startup 的時候全量灌入到內存中,但隨着時間的推移,內存和數據庫的同步偶爾會出現數據差別的狀況,伴隨着就是運營那邊報過來的 bug,檢查數據庫的數據完整性很簡單,直接寫一些 sql 驗證一下就行了,但校驗內存中的數據就很是麻煩了,由於你不能像寫 sql 同樣直接去查生產中的內存集合,那怎麼辦呢? 爲了方便演示問題,先上一段演示代碼:git
class Program { static void Main(string[] args) { var tradeList = new List<Trade>() { new Trade(){TradeID=1, TradeTitle="交易1", Created=Convert.ToDateTime("2020/8/1"), CustomerID=1}, new Trade(){TradeID=2, TradeTitle="交易2", Created=Convert.ToDateTime("2020/8/5"),CustomerID=2}, new Trade(){TradeID=3, TradeTitle="交易3", Created=Convert.ToDateTime("2020/8/10"), CustomerID=3} }; } } class Trade { public int TradeID { get; set; } public string TradeTitle { get; set; } public DateTime Created { get; set; } public int CustomerID { get; set; } }
上面的 tradeList
就是內存中的集合,如今有一個問題,我想查詢一下 trade 表中 CustomerID in (1,2,10) && Created <= '2020-08-01'
的記錄是否和內存中的 tradelist 一致。github
用 sql 驗證太簡單了,直接在查詢分析器裏面寫一下sql 搞定,以下圖:sql
那在 UI 上 怎麼驗證呢?數據庫
這個也是你們最容易想到的,使用多個 if 疊加查詢條件,以下代碼所示:express
static void Main(string[] args) { var tradeList = new List<Trade>() { new Trade(){TradeID=1, TradeTitle="交易1", Created=Convert.ToDateTime("2020/8/1"), CustomerID=1}, new Trade(){TradeID=2, TradeTitle="交易2", Created=Convert.ToDateTime("2020/8/5"),CustomerID=2}, new Trade(){TradeID=3, TradeTitle="交易3", Created=Convert.ToDateTime("2020/8/10"), CustomerID=3} }; IEnumerable<Trade> query = tradeList; //UI var queryCustomerIDList = new List<int>() { 1, 2, 10}; var queryCreated = "2020-08-01"; if (queryCustomerIDList.Count > 0) { query = query.Where(m => queryCustomerIDList.Contains(m.CustomerID)); } if (string.IsNullOrEmpty(queryCreated)) { query = query.Where(m => m.Created <= Convert.ToDateTime(queryCreated)); } //最後的結果 var list = query.ToList(); }
問題貌似是能夠解決,可是這種用 if 疊加的方式不以爲太不靈活了嗎? 若是客戶心情很差,又來了一個 TradeID between 1 and 10
的篩選條件,那上面的代碼是否是還得加一個 TradeID 的判斷 ? 太麻煩了,還得繼續尋找更靈活的姿式。ide
哈哈,你們看到 DataTable 是否是有一點懵逼,可不要小瞧這玩意,人家但是直接支持 sql 查詢的哦,這靈活性不容小覷哈,上一段代碼說話:工具
static void Main(string[] args) { var tradeList = new List<Trade>() { new Trade(){TradeID=1, TradeTitle="交易1", Created=Convert.ToDateTime("2020/8/1"), CustomerID=1}, new Trade(){TradeID=2, TradeTitle="交易2", Created=Convert.ToDateTime("2020/8/5"),CustomerID=1}, new Trade(){TradeID=3, TradeTitle="交易3", Created=Convert.ToDateTime("2020/8/10"), CustomerID=3} }; var table = CopyToDataTable(tradeList); var query = table.Select("CustomerID in (1,2,10) and Created <= '2020-08-01' and TradeID >= 1 and TradeID <= 10") .Select(m => new Trade() { TradeID = Convert.ToInt32(m[0]), TradeTitle = Convert.ToString(m[1]), Created = Convert.ToDateTime(m[2]), CustomerID = Convert.ToInt32(3) }).ToList(); } public static DataTable CopyToDataTable<T>(IEnumerable<T> array) { var ret = new DataTable(); foreach (PropertyDescriptor dp in TypeDescriptor.GetProperties(typeof(T))) ret.Columns.Add(dp.Name); foreach (T item in array) { var Row = ret.NewRow(); foreach (PropertyDescriptor dp in TypeDescriptor.GetProperties(typeof(T))) Row[dp.Name] = dp.GetValue(item); ret.Rows.Add(Row); } return ret; }
是否是很強大,直接將文本化的 sql 塞入到 DataTable 中,你想什麼樣的查詢你就寫什麼樣的 sql 就 ok 啦,固然,理論歸理論,在個人場景中確定是不會這麼玩的,畢竟內存中的 trade 有上千萬行,轉成 DataTable 不是給本身挖坑嘛,那有沒有其餘的方式呢?開發工具
我想不少人看到 表達式樹 都會遠而避之,雖然這玩意很強大,可是太複雜了,它會將你的查詢語句拆解成樹中的節點從而構建一棵很是複雜的樹結構,其實 DataTable 對 sql
語句的解析也是在內存中構建了一棵解析樹,因此這玩意太反人類了,好比你要構建 i > 5
的查詢,你須要下面這樣的硬編碼,這仍是很是簡單的哈,複雜的會讓你吐血。ui
ParameterExpression param = Expression.Parameter(typeof(int), "i"); ConstantExpression constExp = Expression.Constant(5, typeof(int)); BinaryExpression greaterThan = Expression.GreaterThan(param, constExp); Expression<Func<int, bool>> f = Expression.Lambda<Func<int, bool>>(greaterThan, param); Func<int, bool> mydelegate = f.Compile(); Console.WriteLine(mydelegate(5));
從圖中能夠看到,5>5 = False
是沒有問題的,既然表達式樹是能夠解決相似這樣的場景,聰明的你應該會想到,開源社區是否又相似封裝好的 ExpressionTree
開發包呢? 說實話,還真有。。。編碼
開源大法好,github地址:https://github.com/davideicardi/DynamicExpresso , 這玩意實現了 將文本化的 C# 語句 動態轉換成 delegate,這句話是什麼意思呢? 你們能夠看一下這張圖:
從上圖能夠看到,你能夠 寫一些文本化的 C# 語句,而後通過 DynamicExpresso 處理後轉換成了可執行 delegate,若是你沒看懂,我用代碼表示一下,以下圖:
其中: 30 = 5 * 8 / 2 + 10
,重點在於這裏的 數學表達式 是文本的,有了這個思路,那我是否是也能夠將 tradeList
的查詢條件文本化表示,以下代碼:
var interpreter = new Interpreter(); string whereExpression = "(trade.CustomerID == 1 || trade.CustomerID==2 || trade.CustomerID==10) && " + "trade.Created <= Convert.ToDateTime(\"2020-08-01\") &&" + "trade.TradeID >= 1 && " + "trade.TradeID <=10"; Func<Trade, bool> queryFunc = interpreter.ParseAsDelegate<Func<Trade, bool>>(whereExpression, "trade"); var list = tradeList.Where(queryFunc).ToList();
問題搞定,仍是比較完美的 🙂🙂🙂
總的來講,有了DynamicExpresso ,我就能夠將 文本化的 C#語句 直接丟給 Where 條件就能夠靈活檢索,完美的解決了在內存中查詢 tradelist 數據分佈狀況,固然目前的 DynamicExpresso 還有不少語句不支持,不過都在完善中,期待你們支持點贊加貢獻。