Living in the Tech Avalanche Generation

A practitioner’s introspective on technology

Pay attention to where you use your LINQ Queries

Firstly let me say that I love LINQ. However like any technology stack there are always some gotcha’s that you need to watch out for. Deferred execution is one of LINQ’s great features, however if your not thinking clearly when you use LINQ you could be writing some really poorly performing code.

Consider the following code:

[Test]
public void AbstractFactoryAssertions()
{
     ////mock the factory
     var mock = new Mock<Soccer>();
     //setup a shape
     BallShape shape = BallShape.Round;
     BallType style = new BallType(shape, “Synthetic Fibres”);
     //assert correct state
     Assert.AreEqual(mock.Object.GetBallType().Style.
                     ExternalMaterial, “Synthetic Fibres”);
     Assert.AreEqual(mock.Object.GetBallType().Style.Shape, shape);
     Assert.IsTrue(mock.Object.IsPlayedInternationally);

     //***********************
     //THIS IS THE SMELLY CODE
     Assert.AreEqual(mock.Object.ToString().“Proxy”),
                    mock.Object.ToString().Length -
                    “Proxy”)),
                   “Australia England France Poland” +
                   “UnitedStates all play Soccer”);
                   Assert.IsTrue(mock.Object.GetType().BaseType ==
                   typeof(Soccer));
     //END OF THE SMELLY CODE
     //**********************
}

The smelly code above (see code comments), force three calls to the ToString() method of the Soccer class and this method is defined in the Abstract Factory class of FootballCode, which the Soccer class inherits from. It is the code in the ToString() method of FootballCode base class where we strike problems.

public override string ToString()
{
    var exp = from c in PlayedIn
              select c;
    StringBuilder _tostring = new StringBuilder();
    string myname = this.GetType().Name;
    foreach (Country c in exp)
    {
        _tostring.Append(c.GetType().Name + “|”);
    }
    return _tostring.ToString().Replace(‘|’, ‘ ‘) +
                “all play “ + myname;
}

Three calls to the ToString() method in our test will force the LINQ query to execute three times when we only needed to do so once. It’s an obvious mistake but you need to make sure that wherever you have code that will execute a LINQ query, you remain alert as to how you use or call on it. Lets fix the offending code:

//********************************************
//            Fixed Smelly Code

string soccerToString = mock.Object.ToString();
int idxProxy = soccerToString.LastIndexOf(“Proxy”);
int soccerLength = soccerToString.Length;
Assert.AreEqual(mock.Object.ToString().
                Remove(idxProxy, soccerLength - idxProxy),
                “Australia England France Poland” +
                “UnitedStates all play Soccer”);

//            Fixed Smelly Code
//********************************************

By averting the repeated calls to the ToString() method we bypass the redundant execution of the LINQ query. Actually this is a pretty obvious thing to avoid, but developers need to be vigilant as they TDD there way through the design of the object or domain models. For example, I actually should have refactored the code above in the ToString() method and probably moved it into my constructor and stored the result in a private member and continually referenced it’s value from the ToString() method, making the entire process of getting the data static by nature post object creation.

Another thing to pay attention to is awareness of how deferred execution works and staying clear of nested filtering and setting parameters in variables at the correct juncture. For example if we take the following method:

public void ExampleOfLinqBehaviour()
{
    List<Country> engspeakCountries = new List<Country>();

    string _name = “UnitedStates”;

    var query = from c in PlayedIn
                where c.GetType().Name == _name
                select c;

    //the query will use this as
    //the parameter to the where
    //clause and NOT the value
    //of UnitedStates
    _name = “Australia”;

    //query gets executed here in the 
    //foreach loop and therefore the
    //_name parameter is only used once
    //in the case where its value is
    //equal to Austral and NOT UnitedStates.
    foreach (Country c in query)
    {
        engspeakCountries.Add(c);
    }

    var queryFilter = from c in query
                      where c.Region == “Asia Pacific”
                      select c;

    //this will force both linq queries to
    //execute like nested loops. The first query
    //should already probably have executed rather
    //than executing a second time redundantly
    foreach (Country c in queryFilter)
    {
        Console.WriteLine(c.Region);
    }

    //sometimes you will improve performance by
    //forcing the LINQ query to execute
    var queryForced = (from c in PlayedIn
                where c.GetType().Name == _name
                select c).ToList();

    //iterate the list once only
    queryForced.ForEach(c => Console.WriteLine(c.Region));
}

The mistake above lies in the intention to query with a ‘where’ filter that matches on the initial value of the “UnitedStates”, which is subsequently overwritten prematurely with the value of ‘Australia’ and due to the nature of deferred executing, the query will not execute until the first foreach() statement is executed.

The Debugger & Tracing LINQ To SQL

A final cautionary note: if your tracing SQL queries that are being executed by LINQ To SQL and you use the debugger Linq Debuggerto expand the results view before a query has executed, it will in fact force the query to execute, producing the harvested results and by sending the appropriate TSQL command to the Server.

Share/Save/Bookmark

No comments yet. Be the first.

Leave a reply

Creative Commons Attribution-ShareAlike 2.5 Australia
Creative Commons Attribution-ShareAlike 2.5 Australia