java动态构建数据库复杂查询教程
有的时候,你需要动态构建一个比较复杂的查询条件,传入数据库中进行查询。而条件本身可能来自前端请求或者配置文件。那么这个时候,表达式树,就可以帮助到你。
where当中可以传入固定的条件
以下是一个简单的单元测试用例。接下来,我们将这个测试用例改的面目全非。
[test] public void normal() { var re = enumerable.range(0, 10).asqueryable() // 0-9 .where(x => x >= 1 && x < 5).tolist(); // 1 2 3 4 var expectation = enumerable.range(1, 4); // 1 2 3 4 re.should().beequivalentto(expectation); }
queryable中的where就是一种表达式树
由于是 queryable 的关系,所以where当中的其实是一个表达式,那么我们把它单独定义出来,顺便水一下文章的长度。
[test] public void expression00() { expression<func<int, bool>> filter = x => x >= 1 && x < 5; var re = enumerable.range(0, 10).asqueryable() .where(filter).tolist(); var expectation = enumerable.range(1, 4); re.should().beequivalentto(expectation); }
表达式可以通过lambda隐式转换
expression 右侧是一个 lambda ,所以可以捕获上下文中的变量。
这样你便可以把 minvalue 和 maxvalue 单独定义出来。
于是乎你可以从其他地方来获取 minvalue 和 maxvalue 来改变 filter。
[test] public void expression01() { var minvalue = 1; var maxvalue = 5; expression<func<int, bool>> filter = x => x >= minvalue && x < maxvalue; var re = enumerable.range(0, 10).asqueryable() .where(filter).tolist(); var expectation = enumerable.range(1, 4); re.should().beequivalentto(expectation); }
可以使用方法创建表达式
那既然这样,我们也可以使用一个方法来创建 expression。
这个方法,实际上就可以认为是这个 expression 的工厂方法。
[test] public void expression02() { var filter = createfilter(1, 5); var re = enumerable.range(0, 10).asqueryable() .where(filter).tolist(); var expectation = enumerable.range(1, 4); re.should().beequivalentto(expectation); expression<func<int, bool>> createfilter(int minvalue, int maxvalue) { return x => x >= minvalue && x < maxvalue; } }
通过func可以更加灵活的组合条件
那可以使用 minvalue 和 maxvalue 作为参数来制作工厂方法,那么用委托当然也可以。
于是,我们可以把左边和右边分别定义成两个 func,从而由外部来决定左右具体的比较方式。
[test] public void expression03() { var filter = createfilter(x => x >= 1, x => x < 5); var re = enumerable.range(0, 10).asqueryable() .where(filter).tolist(); var expectation = enumerable.range(1, 4); re.should().beequivalentto(expectation); expression<func<int, bool>> createfilter(func<int, bool> leftfunc, func<int, bool> rightfunc) { return x => leftfunc.invoke(x) && rightfunc.invoke(x); } }
也可以手动构建表达式
实际上,左右两个不仅仅是两个func,其实也可以直接是两个表达式。
不过稍微有点不同的是,表达式的合并需要用 expression 类型中的相关方法创建。
我们可以发现,调用的地方这次其实没有任何改变,因为 lambda 既可以隐式转换为 func 也可以隐式转换为 expression。
每个方法的意思可以从注释中看出。
[test] public void expression04() { var filter = createfilter(x => x >= 1, x => x < 5); var re = enumerable.range(0, 10).asqueryable() .where(filter).tolist(); var expectation = enumerable.range(1, 4); re.should().beequivalentto(expectation); expression<func<int, bool>> createfilter(expression<func<int, bool>> leftfunc, expression<func<int, bool>> rightfunc) { // x var pexp = expression.parameter(typeof(int), "x"); // (a => leftfunc(a))(x) var leftexp = expression.invoke(leftfunc, pexp); // (a => rightfunc(a))(x) var rightexp = expression.invoke(rightfunc, pexp); // (a => leftfunc(a))(x) && (a => rightfunc(a))(x) var bodyexp = expression.andalso(leftexp, rightexp); // x => (a => leftfunc(a))(x) && (a => rightfunc(a))(x) var resultexp = expression.lambda<func<int, bool>>(bodyexp, pexp); return resultexp; } }
引入表达式的解构
使其更加简单
但是,上面的方法,其实可以再优化一下。避免对左右表达式的直接调用。
使用一个叫做 unwrap 的方法,可以将 lambda expression 解构成只包含 body 部分的表达式。
这是一个自定义的扩展方法,你可以通过 objectvisitor 来引入这个方法。
[test] public void expression05() { var filter = createfilter(x => x >= 1, x => x < 5); var re = enumerable.range(0, 10).asqueryable() .where(filter).tolist(); var expectation = enumerable.range(1, 4); re.should().beequivalentto(expectation); expression<func<int, bool>> createfilter(expression<func<int, bool>> leftfunc, expression<func<int, bool>> rightfunc) { // x var pexp = expression.parameter(typeof(int), "x"); // leftfunc(x) var leftexp = leftfunc.unwrap(pexp); // rightfunc(x) var rightexp = rightfunc.unwrap(pexp); // leftfunc(x) && rightfunc(x) var bodyexp = expression.andalso(leftexp, rightexp); // x => leftfunc(x) && rightfunc(x) var resultexp = expression.lambda<func<int, bool>>(bodyexp, pexp); return resultexp; } }
可以拼接更多的表达式
我们可以再优化以下,把 createfilter 方法扩展为支持多个子表达式和可自定义子表达式的连接方式。
于是,我们就可以得到一个 joinsubfilters 方法。
[test] public void expression06() { var filter = joinsubfilters(expression.andalso, x => x >= 1, x => x < 5); var re = enumerable.range(0, 10).asqueryable() .where(filter).tolist(); var expectation = enumerable.range(1, 4); re.should().beequivalentto(expectation); expression<func<int, bool>> joinsubfilters(func<expression, expression, expression> expjoiner, params expression<func<int, bool>>[] subfilters) { // x var pexp = expression.parameter(typeof(int), "x"); var result = subfilters[0]; foreach (var sub in subfilters[1..]) { var leftexp = result.unwrap(pexp); var rightexp = sub.unwrap(pexp); var bodyexp = expjoiner(leftexp, rightexp); result = expression.lambda<func<int, bool>>(bodyexp, pexp); } return result; } }
使用工厂方法来代替固定的子表达式
有了前面的经验,我们知道。其实x => x >= 1这个表达式可以通过一个工厂方法来建。
所以,我们使用一个 createminvaluefilter 来创建这个表达式。
[test] public void expression07() { var filter = joinsubfilters(expression.andalso, createminvaluefilter(1), x => x < 5); var re = enumerable.range(0, 10).asqueryable() .where(filter).tolist(); var expectation = enumerable.range(1, 4); re.should().beequivalentto(expectation); expression<func<int, bool>> createminvaluefilter(int minvalue) { return x => x >= minvalue; } expression<func<int, bool>> joinsubfilters(func<expression, expression, expression> expjoiner, params expression<func<int, bool>>[] subfilters) { // x var pexp = expression.parameter(typeof(int), "x"); var result = subfilters[0]; foreach (var sub in subfilters[1..]) { var leftexp = result.unwrap(pexp); var rightexp = sub.unwrap(pexp); var bodyexp = expjoiner(leftexp, rightexp); result = expression.lambda<func<int, bool>>(bodyexp, pexp); } return result; } }
工厂方法内部也可以使用expression手动创建
当然,可以只使用 expression 相关的方法来创建x => x >= 1。
[test] public void expression08() { var filter = joinsubfilters(expression.andalso, createminvaluefilter(1), x => x < 5); var re = enumerable.range(0, 10).asqueryable() .where(filter).tolist(); var expectation = enumerable.range(1, 4); re.should().beequivalentto(expectation); expression<func<int, bool>> createminvaluefilter(int minvalue) { // x var pexp = expression.parameter(typeof(int), "x"); // minvalue var rightexp = expression.constant(minvalue); // x >= minvalue var bodyexp = expression.greaterthanorequal(pexp, rightexp); var result = expression.lambda<func<int, bool>>(bodyexp, pexp); return result; } expression<func<int, bool>> joinsubfilters(func<expression, expression, expression> expjoiner, params expression<func<int, bool>>[] subfilters) { // x var pexp = expression.parameter(typeof(int), "x"); var result = subfilters[0]; foreach (var sub in subfilters[1..]) { var leftexp = result.unwrap(pexp); var rightexp = sub.unwrap(pexp); var bodyexp = expjoiner(leftexp, rightexp); result = expression.lambda<func<int, bool>>(bodyexp, pexp); } return result; } }
同理,子表达式都可以如此创建
那既然都用了 expression 来创建子表达式了,那就干脆再做一点点改进,把x => x < 5也做成从工厂方法获取。
[test] public void expression09() { var filter = joinsubfilters(expression.andalso, createvaluecomparefilter(expression.greaterthanorequal, 1), createvaluecomparefilter(expression.lessthan, 5)); var re = enumerable.range(0, 10).asqueryable() .where(filter).tolist(); var expectation = enumerable.range(1, 4); re.should().beequivalentto(expectation); expression<func<int, bool>> createvaluecomparefilter(func<expression, expression, expression> comparerfunc, int rightvalue) { var pexp = expression.parameter(typeof(int), "x"); var rightexp = expression.constant(rightvalue); var bodyexp = comparerfunc(pexp, rightexp); var result = expression.lambda<func<int, bool>>(bodyexp, pexp); return result; } expression<func<int, bool>> joinsubfilters(func<expression, expression, expression> expjoiner, params expression<func<int, bool>>[] subfilters) { // x var pexp = expression.parameter(typeof(int), "x"); var result = subfilters[0]; foreach (var sub in subfilters[1..]) { var leftexp = result.unwrap(pexp); var rightexp = sub.unwrap(pexp); var bodyexp = expjoiner(leftexp, rightexp); result = expression.lambda<func<int, bool>>(bodyexp, pexp); } return result; } }
加入一点点配置,就完成了
最后,我们在把子表达式的创建通过一点点小技巧。通过外部参数来决定。就基本完成了一个多 and 的值比较查询条件的动态构建。
[test] public void expression10() { var config = new dictionary<string, int> { { ">=", 1 }, { "<", 5 } }; var subfilters = config.select(x => createvaluecomparefilter(mapconfig(x.key), x.value)).toarray(); var filter = joinsubfilters(expression.andalso, subfilters); var re = enumerable.range(0, 10).asqueryable() .where(filter).tolist(); var expectation = enumerable.range(1, 4); re.should().beequivalentto(expectation); func<expression, expression, expression> mapconfig(string op) { return op switch { ">=" => expression.greaterthanorequal, "<" => expression.lessthan, _ => throw new argumentoutofrangeexception(nameof(op)) }; } expression<func<int, bool>> createvaluecomparefilter(func<expression, expression, expression> comparerfunc, int rightvalue) { var pexp = expression.parameter(typeof(int), "x"); var rightexp = expression.constant(rightvalue); var bodyexp = comparerfunc(pexp, rightexp); var result = expression.lambda<func<int, bool>>(bodyexp, pexp); return result; } expression<func<int, bool>> joinsubfilters(func<expression, expression, expression> expjoiner, params expression<func<int, bool>>[] subfilters) { // x var pexp = expression.parameter(typeof(int), "x"); var result = subfilters[0]; foreach (var sub in subfilters[1..]) { var leftexp = result.unwrap(pexp); var rightexp = sub.unwrap(pexp); var bodyexp = expjoiner(leftexp, rightexp); result = expression.lambda<func<int, bool>>(bodyexp, pexp); } return result; } }
以上就是java动态构建数据库复杂查询实现示例的详细内容,更多关于动态构建数据库复杂查询的资料请关注其它相关文章!