java動態(tài)構(gòu)建數(shù)據(jù)庫復雜查詢教程
有的時候,你需要動態(tài)構(gòu)建一個比較復雜的查詢條件,傳入數(shù)據(jù)庫中進行查詢。而條件本身可能來自前端請求或者配置文件。那么這個時候,表達式樹,就可以幫助到你。
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 的關(guān)系,所以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隱式轉(zhuǎn)換
Expression 右側(cè)是一個 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);
}
可以使用方法創(chuàng)建表達式
那既然這樣,我們也可以使用一個方法來創(chuàng)建 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 作為參數(shù)來制作工廠方法,那么用委托當然也可以。
于是,我們可以把左邊和右邊分別定義成兩個 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);
}
}
也可以手動構(gòu)建表達式
實際上,左右兩個不僅僅是兩個Func,其實也可以直接是兩個表達式。
不過稍微有點不同的是,表達式的合并需要用 Expression 類型中的相關(guān)方法創(chuàng)建。
我們可以發(fā)現(xiàn),調(diào)用的地方這次其實沒有任何改變,因為 Lambda 既可以隱式轉(zhuǎn)換為 Func 也可以隱式轉(zhuǎn)換為 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;
}
}
引入表達式的解構(gòu)
使其更加簡單
但是,上面的方法,其實可以再優(yōu)化一下。避免對左右表達式的直接調(diào)用。
使用一個叫做 Unwrap 的方法,可以將 Lambda Expression 解構(gòu)成只包含 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;
}
}
可以拼接更多的表達式
我們可以再優(yōu)化以下,把 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;
}
}
使用工廠方法來代替固定的子表達式
有了前面的經(jīng)驗,我們知道。其實x => x >= 1這個表達式可以通過一個工廠方法來建。
所以,我們使用一個 CreateMinValueFilter 來創(chuàng)建這個表達式。
[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;
}
}
工廠方法內(nèi)部也可以使用Expression手動創(chuàng)建
當然,可以只使用 Expression 相關(guān)的方法來創(chuàng)建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;
}
}
同理,子表達式都可以如此創(chuàng)建
那既然都用了 Expression 來創(chuàng)建子表達式了,那就干脆再做一點點改進,把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;
}
}
加入一點點配置,就完成了
最后,我們在把子表達式的創(chuàng)建通過一點點小技巧。通過外部參數(shù)來決定。就基本完成了一個多 And 的值比較查詢條件的動態(tài)構(gòu)建。
[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動態(tài)構(gòu)建數(shù)據(jù)庫復雜查詢實現(xiàn)示例的詳細內(nèi)容,更多關(guān)于動態(tài)構(gòu)建數(shù)據(jù)庫復雜查詢的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot2.0 整合 SpringSecurity 框架實現(xiàn)用戶權(quán)限安全管理方法
Spring Security是一個能夠為基于Spring的企業(yè)應用系統(tǒng)提供聲明式的安全訪問控制解決方案的安全框架。這篇文章主要介紹了SpringBoot2.0 整合 SpringSecurity 框架,實現(xiàn)用戶權(quán)限安全管理 ,需要的朋友可以參考下2019-07-07
java中MultipartFile和File最簡單的互相轉(zhuǎn)換示例
這篇文章主要給大家介紹了關(guān)于java中MultipartFile和File最簡單的互相轉(zhuǎn)換的相關(guān)資料,MultipartFile和File都是Java中用于處理文件上傳的類,MultipartFile用于處理上傳的文件,File用于處理本地磁盤上的文件,需要的朋友可以參考下2023-09-09
SpringBoot中的JPA(Java?Persistence?API)詳解
這篇文章主要介紹了SpringBoot中的JPA(Java?Persistence?API)詳解,JPA用于將?Java?對象映射到關(guān)系型數(shù)據(jù)庫中,它提供了一種面向?qū)ο蟮姆绞絹聿僮鲾?shù)據(jù)庫,使得開發(fā)者可以更加方便地進行數(shù)據(jù)持久化操作,需要的朋友可以參考下2023-07-07

