It may or may not be overkill, but I'm thinking its not because I spent 2 days racking my brain for another solution and couldn't find one. Let me describe in a little more detail how it works:
Like I said, I've created a sort of abstraction layer above LLBL's predicates, and mimic'd the functionality of the predicates. The base class for everything is SearchPredicate which has the following interface:
Public Interface ISearchPredicate
Property SearchType() As SearchPredicateType
Property ValidOperators() As PredicateOperators
Property Negate() As Boolean
Property Parameters() As ArrayList
Function ToLLBLPredicate() As SD.LLBLGen.Pro.ORMSupportClasses.Predicate
ReadOnly Property PredicateControl() As SearchPredicateControl
Property Parent() As SearchPredicateExpression
End Interface
Then there's a SearchPredicateExpression:
Public Interface ISearchPredicateExpression
Inherits ISearchPredicate
Sub Add(ByVal SearchPredicate As ISearchPredicate)
Sub AddWithAnd(ByVal SearchPredicate As ISearchPredicate)
Sub AddWithOr(ByVal SearchPredicate As ISearchPredicate)
Sub Delete(ByVal SearchPredicate As ISearchPredicate)
Default ReadOnly Property Item(ByVal Index As Integer) As ISearchPredicateExpressionElement
Function Count() As Integer
End Interface
I then have 9 SearchPredicateType's (for lack of a better term) which allow visual construction of the boolean search: TicketType, Location, EnteredBy, EnteredOn, CompletedOn, Description, Comment, TypeCategory, ResolutionCategory. Each of these has a class deriving from SearchPredicate.
Each SearchPredicate has an associated WebControl which is created dynamically at page construction. When the page first loads, an empty SearchPredicateExpression is created (and its associated WebControl as well). The WebControl has a DropDownList which allows you to select one of the 9 PredicateType's or a BooleanGroup (SearchPredicateExpression). When one is selected, the associated Predicate is created and its control is added to the parent SearchPredicateExpression's Predicates collection, and its associated control is added to the controls collection of the SearchPredicateExpressionControl. This allows for visual design of any possible search query for our help-desk system.
The problem I ran into is that serialization without a custom TypeConverter just wasn't working, and to build a TypeConverter I had to be able to recursively convert the predicates to a string for serialization.
I ended up building a tokenizer for parsing, and instead of converting things to non-terminals I just sent the token stream directly to an interpreter which reconstructed the objects. It works amazingly well.
Here's an example of a serialized SearchPredicateExpression:
e[p[b[0];i[1];i[0];l[i[1];];];o[0];p[b[0];i[1];i[0];l[i[1];];];o[0];e[p[b[0];i[1];i[0];l[i[1];];]; o[0];p[b[0];i[1];i[0];l[i[1];];];];o[0];e[p[b[0];i[1];i[0];l[i[1];];];o[0];p[b[0];i[1];i[0];l[i[1];];];o[0]; e[e[p[b[0];i[1];i[0];l[i[1];];];o[0];p[b[0];i[1];i[0];l[i[1];];];];o[0];p[b[0];i[1];i[0];l[i[1];];];o[0]; p[b[0];i[1];i[0];l[i[1];];];];];]
Is this overkill?