Я выполнил совершенно аналогичное требование. Я использую API критериев, потому что вам не нужно использовать строковые операции для объединения запросов, что делает его более стабильным.
Я разместил простой пример в this question, как объединить динамический запрос.
В нашем решении я не разрешил операции ИЛИ, потому что это делает его очень сложным (также часть интерфейса), и производительность может ухудшиться. Но позже мы, вероятно, также реализуем его.
Решения по дизайну должны быть выполнены в соответствии с вашими требованиями и сложностью ваших запросов и структуры данных.
Например, я сделал класс фильтра для каждого запроса. Класс фильтра содержит предопределенные поля. Это делает его более стабильным, потому что вы всегда знаете, какие поля могут быть там, и можете помещать их в определенные места в запрос. Некоторые поля требуют подзапросов. В нашем случае невозможно сделать его полностью универсальным (это означает, что вся информация о том, как должен строиться запрос, хранится в фильтре). Для каждого класса фильтра существует специальный метод, который превращает его в запрос. Это дает вам большую гибкость.
Мои критерии состоят из ссылки на поле, оператора (enum) и аргумента.