一:背景
1. 讲故事
记的在上一家公司做全内存我的项目的时候,因为一些要害表会在程序 startup 的时候全量灌入到内存中,但随着工夫的推移,内存和数据库的同步偶然会呈现数据差别的状况,随同着就是经营那边报过去的 bug,查看数据库的数据完整性很简略,间接写一些 sql 验证一下就好了,但校验内存中的数据就十分麻烦了,因为你不能像写 sql 一样间接去查生产中的内存汇合,那怎么办呢?为了不便演示问题,先上一段演示代码:
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 统一。
用 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));
}
// 最初的后果
var list = query.ToList();}
问题貌似是能够解决,然而这种用 if 叠加的形式不感觉太不灵便了吗?如果客户情绪不好,又来了一个 TradeID between 1 and 10
的筛选条件,那下面的代码是不是还得加一个 TradeID 的判断?太麻烦了,还得持续寻找更灵便的姿态。
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 啦,当然,实践归实践,在我的场景中必定是不会这么玩的,毕竟内存中的 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<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
开发包呢?说实话,还真有。。。
4. DynamicExpresso 开发工具包
开源大法好,github 地址:https://github.com/davideicar…,这玩意实现了 将文本化的 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 <= 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);
问题搞定,还是比拟完满的 ????????????
三:总结
总的来说,有了 DynamicExpresso,我就能够将 文本化的 C# 语句 间接丢给 Where 条件就能够灵便检索,完满的解决了在内存中查问 tradelist 数据分布状况,当然目前的 DynamicExpresso 还有很多语句不反对,不过都在欠缺中,期待大家反对点赞加奉献。