專案需求:MongoDB 存儲多種業務日誌,資料結構各異。需提供查詢介面供開發人員搜尋資料。初期嘗試讓使用者直接輸入 MongoDB JSON 查詢語法或 SQL,但學習成本過高,最終放棄。

iTunes 允許使用者動態新增「條件」與「條件群組」(相當於括號),支援無限層級巢狀邏輯(AND/OR)。這種直觀的視覺化交互完美解決了複雜查詢建構問題。
抽象為兩類實體:
public class QueryGroup
{
public GroupType GroupType { get; set; } // And / Or
public List<QueryItem> Items { get; set; }
public List<QueryGroup> Groups { get; set; }
}
public class QueryItem
{
public string Name { get; set; } // 欄位名
public QuerySymbol OperatorType { get; set; } // 運算子 (=, !=, >, <, LIKE...)
public string Value { get; set; } // 比對值
public DataType ValueType { get; set; } // 資料型別 (String, Int, Double, DateTime)
}
GroupType 決定群組內邏輯為「全部符合」(AND) 或「任一符合」(OR),對應介面的「所有」/「任何」選項。

採用 Bootstrap 建構,將 HTML 拆分為兩個隱藏模板:.query-group-template 與 .query-item-template。
// 初始化:載入一組預設群組,移除其刪除按鈕
$('#queryContainer').append($('.query-group-template>.query-group').clone());
$('#queryContainer>.query-group').first().find('.delete-query-group').remove();
// 新增單一條件
$('button.add-query-item').on('click', function () {
$(this).closest('.query-group').append($('.query-item-template>.query-item').clone());
return false;
});
// 新增子群組(巢狀)
$('button.add-query-group').on('click', function () {
$(this).closest('.query-group').append($('.query-group-template>.query-group').clone());
return false;
});
// 刪除群組(保護根群組不被刪除)
$('button.delete-query-group').on('click', function () {
if (!$(this).closest('.query-group').parent().hasClass('query-group')) return false;
$(this).closest('.query-group').remove();
return false;
});
// 刪除單一條件
$('button.delete-query-item').on('click', function () {
$(this).closest('.query-item').remove();
return false;
});
註:原文使用.live()已棄用,現代專案請改用.on()委派事件。
避免傳遞散亂表單欄位,採用「表單 → JavaScript 物件 → JSON → Base64/URL 編碼」流程:
function QueryGroup() {
this.GroupType = 0;
this.Items = [];
this.Groups = [];
}
function QueryItem() {
this.Name = '';
this.OperatorType = 0;
this.Value = '';
this.ValueType = 0;
}
function GetQueryGroup(group) {
group = $(group);
var queryGroup = new QueryGroup();
queryGroup.GroupType = parseInt(group.find('.group-type').val());
// 處理 Items
group.children('.query-item').each(function () {
var item = new QueryItem();
item.Name = $(this).find('.property-name').val();
item.OperatorType = parseInt($(this).find('.operate-type').val());
item.Value = $(this).find('.query-value').val();
item.ValueType = parseInt($(this).find('.value-type').val());
queryGroup.Items.push(item);
});
// 遞歸處理子 Groups
group.children('.query-group').each(function () {
queryGroup.Groups.push(GetQueryGroup(this));
});
return queryGroup;
}
var rootGroup = GetQueryGroup($('#queryContainer>.query-group').first());
var json = encodeURIComponent(JSON.stringify(rootGroup));
// 送往後端...
var json = Uri.UnescapeDataString(Request["query"]);
var rootGroup = JsonConvert.DeserializeObject<QueryGroup>(json);
public class QueryGroup
{
public GroupType GroupType { get; set; }
public List<QueryItem> Items { get; set; }
public List<QueryGroup> Groups { get; set; }
public ICriteria ToICriteria()
{
ICriteria result = null;
foreach (var criteria in GetICriteriaList())
{
if (result == null) { result = criteria; continue; }
if (GroupType == GroupType.AndAlso) result = result.Add(criteria);
else if (GroupType == GroupType.OrElse) result = result.Or(criteria);
}
return result;
}
private List<ICriteria> GetICriteriaList()
{
var list = new List<ICriteria>();
foreach (var item in Items)
{
list.Add(new Criteria(item.Name, item.OperatorType,
new QueryValue(item.ValueType, item.Value, FieldHierarchyLevel.Child)));
}
foreach (var group in Groups)
{
list.Add(group.ToICriteria()); // 遞歸
}
return list;
}
}
取得 ICriteria 後即可直接傳給資料存取層執行查詢。
IMongoQueryExpression<Func<T, bool>> 供 LINQ 使用
透過「樹狀資料結構 + 模板化前端 + JSON 序列化 + 遞歸轉換」四步走策略,成功實作出支援無限巢狀 AND/OR 邏輯的動態查詢建構器。此架構解耦前後端,擴展性極佳,可快速適配不同資料源與查詢引擎。