Archive for August, 2008

Batching SqlCommand Queries

5 commentsWritten on August 31st, 2008 by
Categories: Performance

As you know, i always like to reduce unnecessary roundtrips. With my Request/Response service layer and my QueryBatcher for NHibernate, it's trivially easy to do so. But what if you're in a situation where you can't use NHibernate and are stuck with low-level SqlCommands? It's actually not hard to enable batching those (select) queries either. (note: for insert/update/delete Commands, there is a better way).

People with a lot of straight-up ADO.NET experience probably already know this, but you can simply combine your select statements into one SqlCommand. When you execute that command, you get the results to each of the queries that was in the command. So far, nothing new here. I'm currently using a Data Access Layer at work which creates SqlCommand objects for select queries that you can build up through an API. Obviously, i'd like a way to use each query in whatever way is best for the specific scenario i'm working on. I basically want to be able to execute the SqlCommand objects as a stand-alone query, or in a batch with other queries, without having to modify the code that creates the SqlCommand objects.

So i wrote a SelectCommandCombiner class which allows you to combine multiple SqlCommands into one SqlCommand, while making sure that none of the parameters conflict with each other. Here's the code:

    public class SelectCommandCombiner

    {

        private readonly List<SqlCommand> commands = new List<SqlCommand>();

 

        public SelectCommandCombiner() : this(new SqlCommand[0]) { }

 

        public SelectCommandCombiner(IEnumerable<SqlCommand> commandsToCombine)

        {

            commands = new List<SqlCommand>();

            if (commandsToCombine != null)

            {

                AddCommands(commandsToCombine);

            }

        }

 

        public void AddCommands(IEnumerable<SqlCommand> commandsToAdd)

        {

            commands.AddRange(commandsToAdd);

        }

 

        public SqlCommand CreateCombinedCommand()

        {

            var combinedCommand = new SqlCommand();

            var combinedCommandText = new StringBuilder();

            var queryIndex = 0;

 

            foreach (var currentCommand in commands)

            {

                var currentCommandText = new StringBuilder(currentCommand.CommandText);

                CreateUniqueParameters(currentCommand, currentCommandText, combinedCommand, queryIndex);

                combinedCommandText.Append(currentCommandText + ";" + System.Environment.NewLine);

                queryIndex++;

            }

 

            combinedCommand.CommandText = combinedCommandText.ToString();

            return combinedCommand;

        }

 

        private static void CreateUniqueParameters(SqlCommand currentCommand, StringBuilder currentCommandText,

            SqlCommand combinedCommand, int queryIndex)

        {

            foreach (SqlParameter parameter in currentCommand.Parameters)

            {

                var clonedParameter = CloneParameter(parameter);

                MakeParameterNameUnique(queryIndex, clonedParameter);

                combinedCommand.Parameters.Add(clonedParameter);

                ReplaceOldNameWithNewName(parameter, clonedParameter, currentCommandText);

            }

        }

 

        private static SqlParameter CloneParameter(ICloneable parameter)

        {

            return (SqlParameter)parameter.Clone();

        }

 

        private static void MakeParameterNameUnique(int queryIndex, SqlParameter clonedParameter)

        {

            var modifiedParameterName = queryIndex + "_" + clonedParameter.ParameterName;

            clonedParameter.ParameterName = modifiedParameterName;

        }

 

        private static void ReplaceOldNameWithNewName(SqlParameter parameter, SqlParameter clonedParameter,

            StringBuilder currentCommandText)

        {

            currentCommandText.Replace("@" + parameter.ParameterName, "@" + clonedParameter.ParameterName);

        }

    }

Now execute the command returned from the CreateCombinedCommand method and you'll get all of the results in one roundtrip. There are a couple of ways to deal with the results... you could simply use a DataReader to loop through all the rows of all the result sets:

                var reader = combinedCommand.ExecuteReader();

 

                do

                {

                    while (reader.Read())

                    {

                        // do something with the values in the current row

                    }

                }

                while (reader.NextResult()); // this moves to the next result set

 

                reader.Close();

Or you could use a SqlDataAdapter to fill a DataSet:

                var adapter = new SqlDataAdapter(combinedCommand);

                var dataSet = new DataSet();

                adapter.Fill(dataSet);

 

                foreach (DataTable table in dataSet.Tables)

                {

                    foreach (DataRow row in table.Rows)

                    {

                        // do something with the values

                    }

                }

In a future post, i'll try to make this as easy to use as the QueryBatcher for NHibernate.

Recommended Books: Clean Code

3 commentsWritten on August 30th, 2008 by
Categories: Books

This week i read Robert C. Martin's Clean Code book. With so many great books already available about writing good code, the first question i asked myself was: do we really need another one? The answer turns out to be YES!

At around 350 pages (depending on which of the appendices you find useful), this book really covers a lot of ground, but in a concise (without being too concise) way. You'll find the typical sound advice on using good names, writing good functions, good vs bad comments, formatting, abstractions, encapsulation, robustness, integrating third-party code, testing, refactoring, class design, system design, enabling emergent design and dealing with concurrency. After all of that, there are a couple of chapters where existing code is improved step by step. To top it all off, the final chapter contains an extensive list of smells and heuristics.

Some of you are probably thinking that all of this looks rather familiar, especially if you've read books like Code Complete, Refactoring and Implementation Patterns. And this book really doesn't offer anything new that nobody ever wrote about before. It is however the best, and most accessible collection of good advice on good code and design that i've read so far.

Tests before code

No Comments »Written on August 28th, 2008 by
Categories: testing

A friend pointed me to one of my own posts from over a year ago about why you should write your tests before your code... At that time, i had about 3 readers so it didn't really get any attention, but i think some people might find it interesting now.

Don't worry... i'm not going to start recycling my old posts so allow me to get away with this for once ;)

Introducing TDD: How would you do it?

14 commentsWritten on August 26th, 2008 by
Categories: ALT.NET, testing

In about 2 weeks, our Belgian ALT.NET group will give a somewhat large presentation on what ALT.NET is and some of the important concepts and practices. As a part of that presentation, i'll give a 10 minute 'lightning-talk' about TDD. I've already got a bit of an idea as to what i'm going to say, but i figured i should ask my readers for some input.

So, if you had to give a short talk (remember, only 10 minutes) about TDD, targeted to an audience that has little to no experience with it, or may not even know anything about it, what are the things you'd really want to highlight? And what are the things that probably aren't worth mentioning for such a short talk?

Or for those of you who have little to no experience with, or knowledge about TDD, what are the things you'd like to hear if someone were to introduce the concept to you in about 10 minutes?

Why The ALT.NET Community Needs To Change Their Ways

13 commentsWritten on August 25th, 2008 by
Categories: ALT.NET

Since its inception, the ALT.NET community has grown a lot. A lot of people have joined the mailinglists and all over the world local ALT.NET communities have been created, with most likely a lot more on the way. That is definitely a good thing. At the same time though, the ALT.NET community has probably put off as many people (if not more) than it has attracted in the first place.

Now, just so everyone is clear on this: i consider myself a part of that community and i definitely want to see it become successful. But for that to happen, i think we need to make some changes. The biggest problem is that we are often perceived as being elitist, and according to some people closed-minded. And honestly, there's a lot of truth to that. Just read our mailinglists, our blog posts, our 'tweets' (i don't use twitter so i don't know if that's the correct way of referring to it but you know what i mean) and a lot of the material we put out there in general. Not everyone in this community is guilty of it, but a lot of us (myself included) are.

In general, we are extremely opinionated and we often state our opinions with a "our way or the highway" kind of mentality. We often outright dismiss approaches or solutions that do not follow our principles and values. And we usually do it in a manner which comes across in a very negative way. And it's really not hard to see why that puts a lot of people off. When someone simply tells me i need to do something in a certain way, my natural reflex is to question it. Why should i do it that way? If i don't get a sound response to that (and one-liners usually don't cut it), the person (or company or whatever) who made the claim immediately loses credibility in my book. I can definitely see how some people have felt that way after hearing/reading certain ALT.NET people making their claims without properly backing them up or simply brushing off any questioning of those claims.

There is absolutely nothing wrong with being opinionated and voicing those opinions. But we really should try to do so in a more positive manner. Simply stating that all developers should use certain practices or principles just doesn't work. We have to formulate our messages better and we probably need to provide more material that properly explains why practice X or principle Y or library Z allows us to achieve better results. And most importantly, we need to make sure we're able to discuss these things with anyone who's not yet 'into it' or even against it, without putting people off. If a lot of people think that we're elitist, closed-minded, frustrated, annoying, or whatever (and they do, just google it) then we are clearly not doing a good job. If we really want to spread these practices and principles that we value so much, we can't afford to rub so many people the wrong way.

As i mentioned in the beginning of my post, i've definitely made these mistakes on quite a few occasions. And i'm gonna try to change that from now on. So if you see me repeat those mistakes, please direct me to this post :)