Skip to main content
2 of 4
Add XML Language syntax
rolfl
  • 98.2k
  • 17
  • 220
  • 419

Fluent Interface for a XmlQueryBuilder

In this answer I suggested using a fluent interface "builder" to replace all the hard-coded, repetitive and quite error-prone inline XML string concatenations.

This code might need a bit of tweaking to work perfectly with the OP's code in the question this code is following-up on, but it seems to work as expected. Thoughts?

public enum XmlQueryOperator
{
    Equals,
    LessThan,
    LessThanOrEqual,
    GreaterThan,
    GreaterThanOrEqual
}
public class XmlQueryBuilder<TEntity>
    where TEntity : class
{
    private readonly XDocument _xDoc = new XDocument();
    private readonly XElement _queryNode = new XElement("query");

    public XmlQueryBuilder()
    {
        var rootNode = new XElement("queryxml");
        rootNode.SetAttributeValue("version", "1.0");

        var entityNode = new XElement("entity");
        entityNode.Value = typeof (TEntity).Name;

        rootNode.Add(entityNode);
        _xDoc.Add(rootNode);
    }

    public XmlQueryBuilder<TEntity> Where<TProperty>(Expression<Func<TEntity, TProperty>> property, XmlQueryOperator operation, string value)
    {
        var xCondition = new XElement("condition");
        var xField = new XElement("field");
        xField.Value = GetPropertyName(property);

        var xExpression = new XElement("expression");
        xExpression.SetAttributeValue("op", operation.ToString());
        xExpression.Value = value;

        xField.Add(xExpression);
        xCondition.Add(xField);

        _queryNode.Add(xCondition);
        return this;
    }

    public override string ToString()
    {
        var parent = _xDoc.Element("queryxml");
        if (parent == null) throw new InvalidOperationException("root node was not created.");
        
        parent.Add(_queryNode);
        return _xDoc.ToString();
    }

    private string GetPropertyName<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertyLambda)
    {
        // credits http://stackoverflow.com/questions/671968/retrieving-property-name-from-lambda-expression/672212#672212 (modified to return property name)
        var type = typeof(TSource);

        var member = propertyLambda.Body as MemberExpression;
        if (member == null)
            throw new ArgumentException(string.Format("Expression '{0}' refers to a method, not a property.", propertyLambda));

        var propInfo = member.Member as PropertyInfo;
        if (propInfo == null)
            throw new ArgumentException(string.Format("Expression '{0}' refers to a field, not a property.", propertyLambda));

        if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType))
            throw new ArgumentException(string.Format("Expresion '{0}' refers to a property that is not from type {1}.", propertyLambda, type));

        return propInfo.Name;
    }
}

Usage - given a TestEntity:

public class TestEntity
{
    public string Test1 { get; set; }
    public int Test2 { get; set; }
    public DateTime Test3 { get; set; }
}

This program produces the expected output:

class Program
{
    static void Main(string[] args)
    {
        var builder = new XmlQueryBuilder<TestEntity>()
            .Where(t => t.Test1, XmlQueryOperator.Equals, "abc")
            .Where(t => t.Test2, XmlQueryOperator.GreaterThan, "def")
            .Where(t => t.Test3, XmlQueryOperator.LessThanOrEqual, DateTime.Now.ToString());

        Console.WriteLine(builder.ToString());
        Console.ReadLine();
    }
}

Output:

<queryxml version="1.0">
  <entity>TestEntity</entity>
  <query>
    <condition>
      <field>Test1<expression op="Equals">abc</expression></field>
    </condition>
    <condition>
      <field>Test2<expression op="GreaterThan">def</expression></field>
    </condition>
    <condition>
      <field>Test3<expression op="LessThanOrEqual">2014-05-05 14:14:57</expression></field>
    </condition>
  </query>
</queryxml>
Mathieu Guindon
  • 75.6k
  • 18
  • 194
  • 468