一:背景
1. 讲故事
记的在上一家公司做全内存项目的时候,因为一些关键表会在程序 startup 的时候全量灌入到内存中,但随着时间的推移,内存和数据库的同步偶尔会出现数据差异的情况,伴随着就是运营那边报过来的 bug,检查数据库的数据完整性很简单,直接写一些 sql 验证一下就好了,但校验内存中的数据就非常麻烦了,因为你不能像写 sql 一样直接去查生产中的内存集合,那怎么办呢?为了方便演示问题,先上一段演示代码:
class Program
{
static void Main(string[] args)
{
var tradeList = new List()
{
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 0)
{
query = query.Where(m => queryCustomerIDList.Contains(m.CustomerID));
}
if (string.IsNullOrEmpty(queryCreated))
{
query = query.Where(m => m.Created 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(IEnumerable 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 不是给自己挖坑嘛,那有没有其他的方式呢?
3. 使用 表达式树 (ExpressionTree)
我想很多人看到 表达式树 都会退避三舍,虽然这玩意很强大,但是太复杂了,它会将你的查询语句拆解成树中的节点从而构建一棵非常复杂的树结构,其实 DataTable 对 sql语句的解析也是在内存中构建了一棵解析树,所以这玩意太反人类了,比如你要构建 i > 5 的查询,你需要下面这样的硬编码,这还是非常简单的哈,复杂的会让你吐血。
ParameterExpression param = Expression.Parameter(typeof(int), "i");
ConstantExpression constExp = Expression.Constant(5, typeof(int));
BinaryExpression greaterThan = Expression.GreaterThan(param, constExp);
Expression f = Expression.Lambda(greaterThan, param);
Func mydelegate = f.Compile();
Console.WriteLine(mydelegate(5));
图片
从图中可以看到,5>5 = False 是没有问题的,既然表达式树是可以解决类似这样的场景,聪明的你应该会想到,开源社区是否又类似封装好的 ExpressionTree 开发包呢?说实话,还真有。。。
4. DynamicExpresso 开发工具包
开源大法好,github地址:https://github.com/davideicardi/DynamicExpresso , 这玩意实现了 将文本化的 C# 语句 动态转换成 delegate,这句话是什么意思呢?大家可以看一下这张图:
图片
从上图可以看到,你可以 写一些文本化的 C# 语句,然后经过 DynamicExpresso 处理后转换成了可执行 delegate,如果你没看懂,我用代码表示一下,如下图:
其中: 30 = 5 * 8 / 2 + 10 ,重点在于这里的 数学表达式 是文本的,有了这个思路,那我是不是也可以将 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 = 1 && " +
"trade.TradeID