Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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>
share|improve this question
3  
I'm not sure I like the name Where. In LINQ, Where means filtering, here it means something quite different. –  svick May 5 at 20:10
1  
What I would probably do is to treat query as a collection of conditions and initialize it as such: new Query<TestEntity> { { t => t.Test1, XmlQueryOperator.Equals, "abc" }, … }. –  svick May 5 at 20:14
    
@svick that's a good idea, I didn't know if OP could have more than just condition nodes though. But I agree if it's all that's needed the fluent interface is probably overkill (although was cool to implement [first-time-I-do-this]). –  Mat's Mug May 5 at 20:16
add comment

2 Answers 2

up vote 2 down vote accepted

One thing I really like about LINQ to XML is that it can be very declarative. For example, this code:

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);

can be alternatively written as:

_xDoc.Add(
    new XElement(
        "queryxml",
        new XAttribute("version", "1.0"),
        new XElement("entity", typeof(TestEntity).Name)));

This approach can be taken too far (e.g. maybe I should have kept the rootNote variable?), but I think that judicious use can make the code more readable, especially since it means the code becomes hierarchical, just like the XML you're building.

share|improve this answer
add comment

ToString() does too many things, it should only be calling _xDoc.ToString(); it has side-effects that make it dangerous to call more than once:

var foo = builder.ToString()
Console.WriteLine(builder.ToString());
Console.ReadLine();

Will not produce the expected output, and will not throw an exception either, resulting in an XML document with _queryNode showing up as many times as ToString() was called.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.