如何校驗內(nèi)存數(shù)據(jù)的一致性,DynamicExpresso 算是幫上大忙了
一:背景
1. 講故事
記的在上一家公司做全內(nèi)存項目的時候,因為一些關(guān)鍵表會在程序 startup 的時候全量灌入到內(nèi)存中,但隨著時間的推移,內(nèi)存和數(shù)據(jù)庫的同步偶爾會出現(xiàn)數(shù)據(jù)差異的情況,伴隨著就是運營那邊報過來的 bug,檢查數(shù)據(jù)庫的數(shù)據(jù)完整性很簡單,直接寫一些 sql 驗證一下就好了,但校驗內(nèi)存中的數(shù)據(jù)就非常麻煩了,因為你不能像寫 sql 一樣直接去查生產(chǎn)中的內(nèi)存集合,那怎么辦呢?為了方便演示問題,先上一段演示代碼:
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 就是內(nèi)存中的集合,現(xiàn)在有一個問題,我想查詢一下 trade 表中 CustomerID in (1,2,10) && Created <= '2020-08-01' 的記錄是否和內(nèi)存中的 tradelist 一致。
用 sql 驗證太簡單了,直接在查詢分析器里面寫一下sql 搞定,如下圖:
圖片
那在 UI 上 怎么驗證呢?
二:尋找解決方法
1. 在UI上自定義高級查詢
這個也是大家最容易想到的,使用多個 if 疊加查詢條件,如下代碼所示:
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));
}
//最后的結(jié)果
var list = query.ToList();
}
圖片
問題貌似是可以解決,但是這種用 if 疊加的方式不覺得太不靈活了嗎?如果客戶心情不好,又來了一個 TradeID between 1 and 10 的篩選條件,那上面的代碼是不是還得加一個 TradeID 的判斷 ?太麻煩了,還得繼續(xù)尋找更靈活的姿勢。
2. 使用DataTable
哈哈,大家看到 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 啦,當(dāng)然,理論歸理論,在我的場景中肯定是不會這么玩的,畢竟內(nèi)存中的 trade 有上千萬行,轉(zhuǎn)成 DataTable 不是給自己挖坑嘛,那有沒有其他的方式呢?
3. 使用 表達式樹 (ExpressionTree)
我想很多人看到 表達式樹 都會退避三舍,雖然這玩意很強大,但是太復(fù)雜了,它會將你的查詢語句拆解成樹中的節(jié)點從而構(gòu)建一棵非常復(fù)雜的樹結(jié)構(gòu),其實 DataTable 對 sql語句的解析也是在內(nèi)存中構(gòu)建了一棵解析樹,所以這玩意太反人類了,比如你要構(gòu)建 i > 5 的查詢,你需要下面這樣的硬編碼,這還是非常簡單的哈,復(fù)雜的會讓你吐血。
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 是沒有問題的,既然表達式樹是可以解決類似這樣的場景,聰明的你應(yīng)該會想到,開源社區(qū)是否又類似封裝好的 ExpressionTree 開發(fā)包呢?說實話,還真有。。。
4. DynamicExpresso 開發(fā)工具包
開源大法好,github地址:https://github.com/davideicardi/DynamicExpresso , 這玩意實現(xiàn)了 將文本化的 C# 語句 動態(tài)轉(zhuǎn)換成 delegate,這句話是什么意思呢?大家可以看一下這張圖:
圖片
從上圖可以看到,你可以 寫一些文本化的 C# 語句,然后經(jīng)過 DynamicExpresso 處理后轉(zhuǎn)換成了可執(zhí)行 delegate,如果你沒看懂,我用代碼表示一下,如下圖:
其中: 30 = 5 * 8 / 2 + 10 ,重點在于這里的 數(shù)學(xué)表達式 是文本的,有了這個思路,那我是不是也可以將 tradeList 的查詢條件文本化表示,如下代碼:
var interpreter = new Interpreter();
interpreter.Reference(typeof(System.Linq.Enumerable));
interpreter.SetVariable("arr", new int[] { 1, 2, 10 });
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();
var i = Enumerable.Contains(new int[] { 1, 2, 3 }, 3);
圖片
問題搞定,還是比較完美的 ??????
三:總結(jié)
總的來說,有了DynamicExpresso ,我就可以將 文本化的 C#語句 直接丟給 Where 條件就可以靈活檢索,完美的解決了在內(nèi)存中查詢 tradelist 數(shù)據(jù)分布情況,當(dāng)然目前的 DynamicExpresso 還有很多語句不支持,不過都在完善中,期待大家支持點贊加貢獻。