views:

107

answers:

3

I'm trying to figure out a way to allow users of my application to define their own queries or filters that can be applied to a collection. I want to eventually provide an intuitive user interface to create these filters (see image below), but initial it would be OK if a user had to type of a text query string. I'll also need to be able to serialized these user defined queries to a string so I can save them with the rest of the project information.

Example UI alt text

I'm looking for the type of functionality you would get out of something like a SQL query such as numeric conditions (less than, greater than), string condition (contains, starts with, ends with), or boolean condition (true or false). I'd also like to be able to group condition using boolean logic like OR, AND, and NOT.

I started drawing out how I would do this from scratch using classes such as NodeFilter, AbsNodeCondition, NodeStringCondition, NodeConditionOrJoin etc.. etc.. But I feel like I'm reinventing the wheel, especially when there's something like Linq available, which I haven't had a chance to spend much time with.

Could I somehow allow a user to enter a Linq to objects query in a text box, and then programaticly turn the string into a real Linq query that can be applied to my collection? Or is there some other way I could allow the user to create and save a query? I'll also need a way to serialize the filter/query to a string so I can save it with the rest of the program information.

A: 

See my answer to a similar question, which covers how to filter on a property selected from a dropdown list.

Bryan Watts
Thanks that helps a lot, but is there a way to serialize/deserialize and Expression<>?
Eric Anastas
If you want to enable raw text input from the user, you will have to do the parsing yourself. Translating from infix notation to expression trees will be a well-known and straightforward algorithm, but you'll still have to do it. A UI like your example is probably more feasible.
Bryan Watts
Expressions are not inherently serializable. A viable design would be to store queries in whatever format makes the most sense for the criteria (i.e. a table to match what's in the dialog box), then translate that to an expression tree whenever you want to run it. This decouples your queries from their executable forms.
Bryan Watts
Ahh ok makes sense. So I'll still use Linq to do the actual query but I could create a custom class that defines how the query is created that is serializable.
Eric Anastas
+1  A: 

This is not necessarily an ideal solution, but the MetaLinq project provides serializable wrappers for the LINQ expression classes.

Here is an example of how you can recreate and execute an expression after round-trip serializing it.

First we serialize an existing lamba expression into an xml document.

var originalExpr = EditableExpression.CreateEditableExpression<string, bool>(
    str => str.Length > 3);
var serializer = new XmlSerializer(originalExpr.GetType());
string xml;

using (var writer = new StringWriter())
{
    serializer.Serialize(writer, originalExpr);
    xml = writer.ToString();
}

Then we deserialize, unwrap and compile it back into a delegate, ready to be executed.

EditableExpression newExpr;

using (var reader = new StringReader(xml))
{
    newExpr = (EditableExpression) serializer.Deserialize(reader);
}

var expr = (Expression<Func<string, bool>>) newExpr.ToExpression();
var items = new[] {"one", "two", "three"};
var result = items.Count(expr.Compile());
Debug.Assert(result == 1);

This is what the serialized expression looks like.

<EditableLambdaExpression xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
  <NodeType>Lambda</NodeType>
  <TypeName>System.Func`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</TypeName>
  <Body xsi:type="EditableBinaryExpression">
    <NodeType>GreaterThan</NodeType>
    <TypeName>System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</TypeName>
    <Left xsi:type="EditableMemberExpression">
      <NodeType>MemberAccess</NodeType>
      <Expression xsi:type="EditableParameterExpression">
        <NodeType>Parameter</NodeType>
        <TypeName>System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</TypeName>
        <Name>str</Name>
      </Expression>
      <MemberName>System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Int32 Length</MemberName>
    </Left>
    <Right xsi:type="EditableConstantExpression">
      <NodeType>Constant</NodeType>
      <Value xsi:type="xsd:int">3</Value>
    </Right>
  </Body>
  <Parameters>
    <EditableExpression xsi:type="EditableParameterExpression">
      <NodeType>Parameter</NodeType>
      <TypeName>System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</TypeName>
      <Name>str</Name>
    </EditableExpression>
  </Parameters>
</EditableLambdaExpression>
Nathan Baulch
A: 

System.Linq.Expressions is unfortunately not serializable, not even in 4.0. You'll have to resort to write your own expression trees and visitors to generate queries from them if you have to cross appdomain boundaries. Otherwise, in box expression trees will be fine.

Apart from that, you may find dynamic linq suiting to your needs somewhat.

Tanveer Badar