The first part to building a general purpose expression parser is to understand what you will be parsing. That is, we need to define a schema for what an expression looks like. In my case, I knew that I wanted to use slightly XML documents that could be interpreted on the server.
I decided to include tokenized values in the format. This would let me define the XML document once and let it work for a wide variety of instances. That is, the notion of today is different depending on when it is being used. Instead of hard coding a date in the document, I use a token named “TODAY” that will replace with the current date at run time.
The next thing I needed to include was a way to identify fields that would be part of the LINQ Where clause. To make it flexible, I wanted to use the dotted notation for linked entities, such as “MyLinkField.ForeignField”. The XML Schema doesn’t really care about this. The parser class will be the one to understand and handle this.
I also needed the user to use hard coded values. that is, the expression field > 5 needs to know that “5” is a simple literal.
The last thing that I wanted to make sure was possible was nesting expressions inside one another. This would allow the possibility for complex expressions such as (ToDoByStaff.Login = TOKEN(User) AND (SubmitDate >= DATEADD(TODAY, DAYS, –5).
Without further ado, here is the schema that I came up with.
And, here is a quick sample expression file that shows off every part of the schema.
Before I leave you for the day, here are some things to consider:
Why didn’t I specify what the valid functions are? I may want to add new ones to the expression parser. That’s also the reason for the other two choices.
And, why does the root expression need to be logical? This statement doesn’t make sense: SELECT * FROM TABLE1 WHERE 1+5. In my implementation, I don’t accept the “NOT” function. Instead, I construct the expressions around that limitation.
Finally, I didn’t put restrictions on the number of children to an expression so that I could handle any arbitrary function that is acceptable to the Entity Framework. I can also expose custom functions like DATEADD that need to be parsed into correct function calls that the framework accepts (in this example, it gets turned into AddDays because the second expression is the literal DAYS).
Next time, we’ll talk about the actual code that runs.
Tags:
Mission App | RIA Services
This is a story of converting an application from ASP, old, old ASP, and RDO into a modern application. It has a few twists and turns and will take much more than a single blog post to tell.
We needed a way to edit data with quite a number of fields. Some of these fields were lookup, others were simple values like dates,. A grid with all items on a single row, like Excel, wouldn’t work. So, the DataForm control came to the rescue. Or, so I thought.
The normal behavior for the control is to give it a collection of all the items being edited. This works fine for small sets. When you have over 10000 items, the initial load takes a long time. Additionally, you end up with a snapshot; you don’t see things getting added to the backing data store until you explicitly ask for it. This wouldn’t work for us.
One thing that our old system did was let you create custom filters on the data. Granted, this was back before we took security seriously. I just let the end user type in any old WHERE clause that got added to my SQL query. Yeah. I wasn’t going to do that with this updated version.
So, I went down the road of learning how to filter data coming from and Entity Framework LINQ statement. I didn’t want to hard code anything.
I came up with an XML schema that would encompass any type of filter that I could think of. Next, I set about writing code that would take an IQueryable object and return another one that had all of the conditions defined in an XML document. In my next post, I’ll start to discuss how I did that. And, I’m sure you’ll find things that I could’ve done better. This was my first time working at this level with the technology.
My next task was loading items from the server only when necessary. This meant creating a new VCR control that could call RIA Service methods to retrieve counts and individual items. I’ll discuss that one in a future post.
Mission App | RIA Services | Silverlight