Joins in LINQ

The scenario: you have two related collections of objects, and you need to smush ’em together into a collection of combined records. It’s easy to do with LINQ’s Join method, but Join can seem a little intimidating–just check out its declaration:

// yikes!
public static IEnumerable Join<TOuter, TInner, TKey, TResult>(
	this IEnumerable<TOuter> outer,
	IEnumerable<TInner> inner,
	Func<TOuter, TKey> outerKeySelector,
	Func<TInner, TKey> innerKeySelector,
	Func<TOuter, TInner, TResult> resultSelector
)

It’s really not so bad, though. Here’s the breakdown:

  • “this IEnumerable<TOuter> outer” what you’re joining from
  • “IEnumerable<TInner> inner” what you’re joining to
  • “Func<TOuter, TKey> outerKeySelector” an expression for how to match the ‘from’ records
  • “Func<TInner, TKey> innerKeySelector” an expression for how to match the ‘to’ records
  • “Func<TOuter, TInner, TResult> resultSelector” an expression for the joined result

Still sounds rough? Let’s look at an easy example:

class Person
{
	public string Name;
	public string Occupation;
}

class Job
{
	public string Name;
	public decimal Salary;
}

void Main()
{
	var people = new[]
	{
		new Person { Name = "Adam", Occupation = "Blogger" },
		new Person { Name = "Joe", Occupation = "Teacher" },
		new Person { Name = "Hilary", Occupation = "Actress" }
	};
	var jobs = new[]
	{
		new Job { Name = "Blogger", Salary = 0.0m },
		new Job { Name = "Teacher", Salary = 100.0m },
		new Job { Name = "Actress", Salary = 5000.0m }
	};

	var salaryByPerson = people.Join(
		jobs,
		p => p.Occupation,
		j => j.Name,
		(p,j) => new { Person = p.Name, Salary = j.Salary });

	foreach (var sbp in salaryByPerson)
	{
		Console.WriteLine("Person: {0}, Salary: {1}",
			sbp.Person,
			sbp.Salary.ToString("c"));
	}
}

/* Output
Person: Adam, Salary: $0.00
Person: Joe, Salary: $100.00
Person: Hilary, Salary: $5,000.00
*/

The Join in the above example is equivalent to SQL like this:

SELECT p.Name AS Person, j.Salary
FROM people p
JOIN jobs j ON p.Occupation=j.Name

Now you’ve got it, right? Yea!

ScenarioContext in SpecFlow

I’ve been using SpecFlow pretty regularly for a few weeks now, and I must say, I’m a fan. I find that it’s a lot easier to do test-first development because I’m writing the test in human-readable, business language.

One of the ideas that I didn’t understand right away was how to re-use generated step definitions across features and scenarios. ScenarioContext and FeatureContext give you great options to handle this, though. Let’s check out an example using a modified version of the default scenario SpecFlow generates with a new feature file:

Scenario: Add two numbers
	Given I enter 50 into the calculator
	And I press plus
	And I enter 70 into the calculator
	When I press enter
	Then the result should be 120 be displayed

When I generate step definitions, I might end up with a class that looks like this:

namespace adamprescott.net.Calculator.SpecFlow
{
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using TechTalk.SpecFlow;

    [Binding]
    public class Calculator_AddSteps
    {
        Calculator calculator = new Calculator();

        [Given(@"I enter (.*) into the calculator")]
        public void GivenIEnterIntoTheCalculator(int p0)
        {
            calculator.Number(p0);
        }

        [Given(@"I press plus")]
        public void GivenIPressPlus()
        {
            calculator.Plus();
        }

        [When(@"I press enter")]
        public void WhenIPressEnter()
        {
            calculator.Enter();
        }

        [Then(@"the result should be (.*) be displayed")]
        public void ThenTheResultShouldBeBeDisplayed(int p0)
        {
            Assert.AreEqual(Convert.ToString(p0), calculator.Display);
        }
    }
}

Okay, not bad. A logical next feature might be subtraction. Some of the steps, like entering numbers and pressing enter, are shared. It would be nice if we could re-use those, but they’re configured to manipulate private variables in the Calculator_AddSteps class. So let’s do some refactoring! Instead of using a member-level variable, I can store my Calculator object in the ScenarioContext, making it accessible to other steps being executed in the same scenario.

// store to ScenarioContext like this:
ScenarioContext.Current.Set<Calculator>(new Calculator());
ScenarioContext.Current.Set<Calculator>(new Calculator(), "Calc");
ScenarioContext.Current["Calc"] = new Calculator();

// retrieve from ScenarioContext like this:
var c = ScenarioContext.Current.Get<Calculator>();
var c = ScenarioContext.Current.Get<Calculator>("Calc");
var c = ScenarioContext.Current["Calc"] as Calculator;

This is overkill for such a simple example, but I separated my shared steps into a new step definitions file. The final solution has three classes for the step definitions: Calculator_AddSteps, Calculator_SubtractSteps, and Calculator_SharedSteps.

Here’s the final solution, broken up by file:

Calculator_Add.feature

Feature: Calculator_Add
	In order to avoid silly mistakes
	As a math idiot
	I want to be told the sum of two numbers

Background:
	Given I have a calculator

Scenario: Add two numbers
	Given I enter 50 into the calculator
	And I press plus
	And I enter 70 into the calculator
	When I press enter
	Then the result should be 120 be displayed

Calculator_AddSteps.cs

namespace adamprescott.net.Calculator.SpecFlow
{
    using TechTalk.SpecFlow;

    [Binding]
    public class Calculator_AddSteps
    {
        [Given(@"I press plus")]
        public void GivenIPressPlus()
        {
            ScenarioContext.Current.Get<Calculator>().Plus();
        }
    }
}

Calculator_SharedSteps.cs

namespace adamprescott.net.Calculator.SpecFlow
{
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using System;
    using TechTalk.SpecFlow;

    [Binding]
    public class Calculator_SharedSteps
    {
        [Given(@"I have a calculator")]
        public void GivenIHaveACalculator()
        {
            ScenarioContext.Current.Set<Calculator>(new Calculator());
        }

        [Given(@"I enter (.*) into the calculator")]
        public void GivenIEnterIntoTheCalculator(int p0)
        {
            ScenarioContext.Current.Get<Calculator>().Number(p0);
        }

        [When(@"I press enter")]
        public void WhenIPressEnter()
        {
            ScenarioContext.Current.Get<Calculator>().Enter();
        }

        [Then(@"the result should be (.*) be displayed")]
        public void ThenTheResultShouldBeBeDisplayed(int p0)
        {
            Assert.AreEqual(Convert.ToString(p0), ScenarioContext.Current.Get<Calculator>().Display);
        }
    }
}

Calculator_Subtract.feature

Feature: Calculator_Subtract
	In order to avoid silly mistakes
	As a math idiot
	I want to be told the difference between two numbers

Background:
	Given I have a calculator

Scenario: Subtract two numbers
	Given I enter 70 into the calculator
	And I press minus
	And I enter 50 into the calculator
	When I press enter
	Then the result should be 20 be displayed

Calculator_SubtractSteps.cs

namespace adamprescott.net.Calculator.SpecFlow
{
    using TechTalk.SpecFlow;

    [Binding]
    public class Calculator_SubtractSteps
    {
        [Given(@"I press minus")]
        public void GivenIPressMinus()
        {
            ScenarioContext.Current.Get<Calculator>().Minus();
        }
    }
}

.NET Rocks! Road Trip

.NET Rocks! is a popular internet talk show, and they’re taking the show on the road. Carl Franklin and Richard Campbell are traversing the country on their .NET Rocks! Visual Studio 2012 Launch Road Trip, making stops in over 37 different cities. I was able to attend their Detroit session in Southfield, MI yesterday. The event was hosted by the Great Lakes Area .NET User Group (with yummy BBQ provided by sponsor New World Systems), and it was a good time!

Carl gave a good talk and demo on creating modern, Windows Store app-style (formerly “metro-style”) apps. In the demo, a slick app was built to display and listen to .NET Rocks! shows using a simple web service. The code was minimal, but the app looked great thanks to the built-in styles–it was impressive to see how little code it takes to make a great looking Windows Store app.

Richard then took over and gave a talk about DevOps. DevOps blurs the line between IT professionals and developers, allowing them to coordinate efforts to evolve and improve applications over time. A big focus of this talk was on Microsoft System Center, Microsoft’s enterprise resource monitoring tool. System Center allows you to collect logs and events from different resources across the network and puts them into a consolidated view. New to System Center is the ability to hook into .NET apps, allowing you to specify and create events for things like methods that take too long. System Center can tie into TFS, so when an application event occurs, a work item can be created.

Third to take the stage was Microsoft’s Jeff Wilcox, independent creator of the popular Foursquare client 4th & Mayor. Jeff talked to us his experience with Microsoft, but most of the discussion was about 4th & Mayor. I’m not a Foursquare user, but I can appreciate what Jeff has done in creating this beautiful app with a focus on providing a great user experience. Jeff’s brief presentation was followed by a live taping of The Tablet Show, another offering from Franklin and Campbell.

It was a fun event. The food was great, it was entertaining, and I learned a few new things. Thanks to GANG for hosting and to .NET Rocks! for stopping by Detroit!

My Foray into Jelly Bean

I’ve been a happy Android (Epic 4G Touch/Galaxy SII) user ever since I made the switch from WP7 (Samsung Focus). Since that time, my wife has switched to an iPhone. I was secretly jealous of features like Siri. Whenever we needed to remember to do something, I’d tell me wife to have Siri set up a reminder because it was so easy and convenient. I was still dedicated to Android because of the free mobile hotspot and turn-by-turn navigation, but I was feeling like my next phone should be an iPhone.

That all changed yesterday when I upgraded to Jelly Bean.

This was only the second time I’ve installed a custom ROM, and I was admittedly nervous. Part of the reason for my initial switch to Android was to get onto a custom ROM to unlock the free mobile hotspot. I stumbled through that, and hadn’t had problems since. I was nervous to do it again because of the satisfaction achieved in my first attempt.

I ran into some bumps along the way. Most significantly, I ran into the error described here when flashing the ROM. The solution offered by one of the replies got me over the hump, though, and it was smooth sailing from there. I’m now up and running on CM10 Alpha 5.3, which can be found here. It’s great!

Everything about Jelly Bean feels new and clean. The visuals and animations look and feel crisp and smooth. The every features like messaging, alerts, and email look better. I haven’t noticed any feature changes with those, but I also haven’t done more exploration.

What I’m most excited about is Google Now, Google’s answer to Siri. I can now ask my phone to set reminders, look-up directions, and send text messages. One of the features that Now boasts is “no digging required.” It’s supposed to learn what information I need and present it to me without asking. I’m very curious to see how this works. But for now, I’m happy with the voice search and command capabilities offered by Now. I can also hold the search button to initiate a voice command from within any screen–very Siri-like.

My allegiance and excitement have been renewed, and my gravitation toward iPhone has been killed. Sorry, Apple.

TransactionScope in Multi-Threaded Applications

Using the TransactionScope class is a terrific way to do implicit transactional programming. It’s incredibly simple, and transactions can span multiple operations across multiple connections.

Basic usage couldn’t be simpler:

using (var ts = new TransactionScope())
{
    // do transactional stuff

    ts.Complete();
}

I was working on an application that accepted a batch of items to be processed asynchronously. The desired behavior was to rollback everything if any individual items failed. I figured this would be a snap with TransactionScope. I wrapped the multi-threaded processing code in a TransactionScope, but it didn’t work. The problem is that the TransactionScope does not transcend application and thread boundaries, so the operations launched on a separate thread were executed outside the scope of the ambient transaction.

There is a relatively simple solution to this problem, though. A DependentTransaction can be created from the current transaction and passed into the sub-processes. Here is a simple example:

public void Parent()
{
    using (var ts = new TransactionScope())
    {
        ThreadPool.QueueUserWorkItem(
            Child,
            Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
        ts.Complete();
    }
}

public void Child(object o)
{
    var dtx = o as DependentTransaction;
    using (var ts = new TransactionScope(dtx))
    {
        // do transactional stuff

        ts.Complete();
    }
    dtx.Complete();
}

Resolve Generic Types at Runtime with Unity

I’m working on a small project that uses Unity to manage record type-specific data providers. As a brief overview, the project accepts a collection of objects. An appropriate data provider is retrieved for each object, and then the object is passed to that provider for processing.

I register the providers in a UnityContainer like so:

UnityContainer.RegisterType<IDataProvider<RecordType>, RecordTypeDataProvider>();

So, in a perfect world, I wanted to resolve the providers based on the type of each record in a collection. Something like this:

foreach (var record in collection)
{
    var provider = ResolveProvider(record);
    provider.Process(record);
}

The problem with this approach is that I need to resolve the type using the record’s type.

// what I want (invalid)
UnityContainer.Resolve<IDataProvider<record.GetType()>>();

Luckily, there’s a way to do this by using the Type.MakeGenericType method. Check it out:

var providerType = typeof(IDataProvider<>).MakeGenericType(record.GetType());
var provider = UnityContainer.Resolve(providerType);

This works, but it leaves me with another challenge. UnityContainer.Resolve returns an object, but I need an IDataProvider. The solution? A base interface containing the non-generic methods and properties. This allows me to resolve the data provider using the record type but still return a typed object. Here’s the complete solution:

public interface IDataProvider
{
    void Process(BaseType record);
}

public interface IDataProvider<T> : IDataProvider
    where T : BaseType
{
    void Process<T>(T record);
}

public class RecordTypeDataProvider : IDataProvider<RecordType>
{
	// IDataProvider.Process
	public void Process(BaseType record)
	{
		Process(record as RecordType);
	}

	// IDataProvider<RecordType>.Process
	public void Process<RecordType>(RecordType record)
	{
		// process it!
	}
}

public static class Broker
{
	private static readonly UnityContainer UnityContainer;

	static Broker()
	{
		UnityContainer = new UnityContainer();

		UnityContainer.RegisterType<IDataProvider<RecordType>, RecordTypeDataProvider>();
		// other providers...
	}

	private static IDataProvider Resolve(BaseType record)
	{
		var providerType = typeof(IDataProvider<>).MakeGenericType(record.GetType());
		return UnityContainer.Resolve(providerType) as IDataProvider;
	}

	public static void Process(IEnumerable<BaseType> collection)
	{
		foreach (var record in collection)
		{
			var provider = Resolve(record);
			provider.Process(record);
		}
	}
}

Encryption 101: Symmetric Algorithms

Earlier this week, we covered one-way encryption using a hash algorithm and a salted hash. If you need to decrypt what you’ve encrypted, these solutions are out the window. The simplest solution for encrypting and decrypting is to use a symmetric algorithm. Data is encrypted using a secret key, and it’s decrypted the same way–using the secret key. (Symmetric!)

It requires a few more steps to accomplish, but overall, it’s still pretty simple. Let’s breakdown the steps:

  1. Select an algorithm
  2. Create a key
  3. Encrypt data
  4. Decrypt data

That doesn’t sound so bad, right!? So let’s do it!

Select an algorithm

The .NET Framework supports a number of different symmetric algorithms, including DES, RC2, Rijndael, and TripleDES. We’ll use TripleDES in this example, so let’s instantiate our crypto provider.

var algorithm = new TripleDESCryptoServiceProvider();

Create a key

Now that we have our crypto provider, creating our secret key is a breeze.

algorithm.GenerateKey();

But wait! That’s not quite it. We also need to generate an initialization vector (IV), which acts as a “randomizer” for the encryption. Don’t worry, though–it’s just as easy as generating the key.

algorithm.GenerateIV();

Encrypt data

We have our key and IV, so we’re ready to encrypt some data! This is a little more complicated, but still not too bad. All we’re doing is creating an ICryptoTransform “encryptor” from our symmetric crypto provider, then using it to write bytes to a CryptoStream. Sounds hard, but it’s really not so bad.

string Encrypt(SymmetricAlgorithm sa, string text)
{
    var encryptor = sa.CreateEncryptor(sa.Key, sa.IV);
    var bytes = Encoding.UTF8.GetBytes(text);
    using (var ms = new MemoryStream())
    {
        using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
        {
            cs.Write(bytes, 0, bytes.Length);
            cs.FlushFinalBlock();
        }
        return Convert.ToBase64String(ms.ToArray());
    }
}

Decrypt data

Now how do we go about decrypting it? Well, not surprisingly–this being a symmetric algorithm and all–it’s nearly identical to encrypting the data. The only difference is that we create a “decryptor” from our crypto provider.

string Decrypt(SymmetricAlgorithm sa, string encrypted)
{
    var decryptor = sa.CreateDecryptor(sa.Key, sa.IV);
    var bytes = Convert.FromBase64String(encrypted);
    using (var ms = new MemoryStream())
    {
        using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write))
        {
            cs.Write(bytes, 0, bytes.Length);
            cs.FlushFinalBlock();
        }
        return Encoding.UTF8.GetString(ms.ToArray());
    }
}

Putting it all together

Overall, symmetric encryption gives us a quick and easy way to do reasonably secure encryption. To properly decrypt data across applications, you’ll need to share the key and IV that you generated. Make sure to keep these values a secret, though, as they allow anybody to decrypt your sensitive data. Here’s the full example from the pieces above:

namespace adamprescott.net.EncryptionSymmetric
{
    using System;
    using System.IO;
    using System.Security.Cryptography;
    using System.Text;

    class Program
    {
        static void Main(string[] args)
        {
            var p = new Program();
            p.Run();
        }

        void Run()
        {
            Console.Write("Input: ");
            var input = Console.ReadLine();

            using (var algorithm = new TripleDESCryptoServiceProvider())
            {
                algorithm.GenerateKey();
                algorithm.GenerateIV();

                var encrypted = Encrypt(algorithm, input);
                Console.WriteLine("Encrypted: {0}", encrypted);

                var decrypted = Decrypt(algorithm, encrypted);
                Console.WriteLine("Decrypted: {0}", decrypted);
            }

            Console.ReadLine();
        }

        string Encrypt(SymmetricAlgorithm sa, string text)
        {
            var encryptor = sa.CreateEncryptor(sa.Key, sa.IV);
            var bytes = Encoding.UTF8.GetBytes(text);
            using (var ms = new MemoryStream())
            {
                using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                {
                    cs.Write(bytes, 0, bytes.Length);
                    cs.FlushFinalBlock();
                }
                return Convert.ToBase64String(ms.ToArray());
            }
        }

        string Decrypt(SymmetricAlgorithm sa, string encrypted)
        {
            var decryptor = sa.CreateDecryptor(sa.Key, sa.IV);
            var bytes = Convert.FromBase64String(encrypted);
            using (var ms = new MemoryStream())
            {
                using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write))
                {
                    cs.Write(bytes, 0, bytes.Length);
                    cs.FlushFinalBlock();
                }
                return Encoding.UTF8.GetString(ms.ToArray());
            }
        }
    }
}

Encryption 101: This Hash Needs Salt

Yesterday, I wrote about how to do simple, one-way encryption using a hash algorithm. This is terrific for encrypting data that doesn’t need to be decrypted, like user passwords. The problem with hash algorithms is that the same values encrypt to the same encrypted values. This is bad because it is possible to see that two encrypted values are the same.

But do not worry. You can turn a hash into a salted hash with one very easy adjustment: add some random bytes to the value. Of course, in order for this to be effective, you’ll also need to store the salt value. When creating the hash for storage, you’ll use salt+value. When creating the hash for comparison, you’ll use salt+value. Get it?

Here’s a short example that uses the RNGCryptoServiceProvider class (RNG = Random Number Generator) to create a salt:

namespace adamprescott.net.EncryptionSaltedHash
{
    using System;
    using System.Security.Cryptography;
    using System.Text;

    class Program
    {
        static void Main(string[] args)
        {
            var p = new Program();
            p.Run();
        }

        private void Run()
        {
            Console.Write("Input: ");
            var input = Console.ReadLine();

            var salt = GetSalt();
            var hashed = HashText(salt + input);
            Console.WriteLine("Salt: {0}", salt);
            Console.WriteLine("Hashed: {0}", HashText(input));

            Console.WriteLine();
            Console.Write("What did you just enter?: ");
            input = Console.ReadLine();
            if (string.Equals(hashed, HashText(salt + input)))
            {
                Console.WriteLine("You are an honest person.");
            }
            else
            {
                Console.WriteLine("You are a liar!");
            }

            Console.ReadLine();
        }

        private string HashText(string text)
        {
            using (var md5 = new MD5CryptoServiceProvider())
            {
                var bytes = Encoding.UTF8.GetBytes(text);
                var hash = md5.ComputeHash(bytes);
                return Convert.ToBase64String(hash);
            }
        }

        private string GetSalt()
        {
            using (var rng = new RNGCryptoServiceProvider())
            {
                var bytes = new byte[8];
                rng.GetBytes(bytes);
                return Convert.ToBase64String(bytes);
            }
        }
    }
}

Encryption 101: Getting Your Hash On

Encryption can be a daunting topic. It’s mostly used only when needed, and it’s typically needed when sensitive data needs to be protected. There are a lot of different ways to encrypt data, and even “encryption made simple” articles can get very complicated. But, like many topics, it’s really not as difficult as it seems.

Part of simplifying the solution lies in identifying your needs. Let’s look at a very easy scenario: encrypting passwords. Obviously, you don’t want to store passwords in plain text. I’ve inherited systems with plain text passwords, and I think it’s flat-out embarrassing. The seemingly obvious solution would be to encrypt a password before saving it and then decrypt it for comparison during the authentication process. That’s over-complicating it, though; what you need is a hash!

Using a hash algorithm is a great way to create one-way encryption. This is ideal for a scenario like passwords. Encrypt the password, and store the encrypted value. When it’s time to authenticate, encrypt the user input and compare. If the encrypted strings match, so do the passwords.

Here’s an easy way to do MD5 encryption in C#:

namespace adamprescott.net.EncryptionHash
{
    using System;
    using System.Security.Cryptography;
    using System.Text;

    class Program
    {
        static void Main(string[] args)
        {
            var p = new Program();
            p.Run();
        }

        private void Run()
        {
            Console.Write("Input: ");
            var input = Console.ReadLine();
            Console.WriteLine("Hashed: {0}", HashText(input));
            Console.ReadLine();
        }

        private string HashText(string text)
        {
            using (var md5 = new MD5CryptoServiceProvider())
            {
                var bytes = Encoding.UTF8.GetBytes(text);
                var hash = md5.ComputeHash(bytes);
                return Convert.ToBase64String(hash);
            }
        }
    }
}

Want to use a different hash algorithm? No problem! Just change the CryptoServiceProvider. Here’s the same example using SHA1:

namespace adamprescott.net.EncryptionHash
{
    using System;
    using System.Security.Cryptography;
    using System.Text;

    class Program
    {
        static void Main(string[] args)
        {
            var p = new Program();
            p.Run();
        }

        private void Run()
        {
            Console.Write("Input: ");
            var input = Console.ReadLine();
            Console.WriteLine("Hashed: {0}", HashText(input));
            Console.ReadLine();
        }

        private string HashText(string text)
        {
            using (var sha1 = new SHA1CryptoServiceProvider())
            {
                var bytes = Encoding.UTF8.GetBytes(text);
                var hash = sha1.ComputeHash(bytes);
                return Convert.ToBase64String(hash);
            }
        }
    }
}

SqlCommand Parameter Caching

Last week, I wrote a short article about the SqlCommandBuilder.DeriveParameters method that can be used to automatically populate the parameters of a stored procedure command. One of the downsides to this approach is that each call to the method will result in a call to the database. Since stored procedure parameters aren’t typically changing very often, this can result in a lot of unnecessary chatter. So what’s the solution? Cache the parameter collection, of course!

Here’s a very simple way to do just that. I have an abstract class that maintains a static dictionary of SqlParameterCollections, indexed by SQL command. When the static DeriveParameters method is called from within derived classes, the dictionary is checked. If a matching parameter collection is found, it will be used to create a new parameter collection using the existing collection as a blueprint. If no collection is found, SqlCommandBuilder.DeriveParameters is used, and the resulting collection is cached.

public abstract class BaseDataProvider
{
    private static readonly Dictionary<string, SqlParameterCollection> _sqlParameterCollections;

    static BaseDataProvider()
    {
        _sqlParameterCollections = new Dictionary<string, SqlParameterCollection>();
    }

    private static void DeriveParameters(SqlCommand cmd)
    {
        if (_sqlParameterCollections.ContainsKey(cmd.CommandText))
        {
            var paramCollection = _sqlParameterCollections[cmd.CommandText];
            cmd.Parameters.AddRange(
                paramCollection.OfType<SqlParameter>()
                    .Select(x => new SqlParameter
                    {
                        ParameterName = x.ParameterName,
                        SqlDbType = x.SqlDbType,
                        Size = x.Size,
                        Direction = x.Direction
                    })
                    .ToArray()
                );
            return;
        }

        SqlCommandBuilder.DeriveParameters(cmd);
        _sqlParameterCollections.Add(cmd.CommandText, cmd.Parameters);
    }
}

Note that caching stored procedure parameters can have a negative side-effect: if the parameters DO change, your cache may need to be refreshed. This could be handled in a number of ways. You could have the cached parameters expire after a certain amount of time, you could have a manual trigger, or you could build logic into your application that automatically refreshes the cache if the appropriate error condition is detected.

Update 10/10/2012:

The SqlParameter class also implements ICloneable. I’ve updated my caching logic to take advantage of this. Note that you must cast the parameter to ICloneable in order to invoke its Clone method.

private static readonly Dictionary<string, SqlParameter[]> ParameterCache;

// retrieve from cache
if (ParameterCache.ContainsKey(cmd.CommandText))
{
	var paramCollection = ParameterCache[cmd.CommandText];
	cmd.Parameters.AddRange(
		paramCollection.OfType<SqlParameter>()
			.Select(x => ((ICloneable)x).Clone() as SqlParameter)
			.ToArray()
		);
	return;
}

// add to cache
var parameters = cmd.Parameters
	.OfType<SqlParameter>()
	.Select(x => ((ICloneable)x).Clone() as SqlParameter)
	.ToArray();
ParameterCache.Add(cmd.CommandText, parameters);