Get All ProgID on System for COM Automation

If you want to use Silverlight COM Automation, you need to know the ProgID of your COM component. These are buried in the registry. Here is a snippet that I found (don’t remember where) and modified a bit to do this:

var regClis = Registry.ClassesRoot.OpenSubKey("CLSID");
var progs = new List<string>();

foreach (var clsid in regClis.GetSubKeyNames()) {
	var regClsidKey = regClis.OpenSubKey(clsid);
	var ProgID = regClsidKey.OpenSubKey("ProgID");
	var regPath = regClsidKey.OpenSubKey("InprocServer32");

	if (regPath == null)
		regPath = regClsidKey.OpenSubKey("LocalServer32");

	if (regPath != null && ProgID != null) {
		var pid = ProgID.GetValue("");
		var filePath = regPath.GetValue("");
		progs.Add(pid + " -> " + filePath);
		regPath.Close();
	}

	regClsidKey.Close();
}

regClis.Close();

progs.Sort();

var sw = new StreamWriter(@"c:\ProgIDs.txt");
foreach (var line in progs)
	sw.WriteLine(line);

sw.Close();

Are you a Git user? Let me help you make project management with Git simple. Checkout Gitpilot.

Follow me on Twitter: @jprichardson and read my blog on entrepreneurship: Techneur

-JP

Silverlight 4 and COM/ActiveX Integration

Silverlight 4 supports COM/ActiveX controls with Trusted permissions. However you must configure your app to be an Out-of-Browser app with Trusted permissions.

Here is the snippet of code that will open Excel and place a value in the first cell:

dynamic ex = AutomationFactory.CreateObject("Excel.Application");
ex.Visible = true;
ex.workbooks.Add();

dynamic sheet = ex.ActiveSheet;
sheet.Cells[1, 1].Value = "hello!";

However this won’t compile out of the box. First make sure that your app is enabled to run as an Out-of-Browser app. Open up your project properties and on the “Silverlight” tab make sure that “Enable running application out of the browser” and then click the button “Out-of-Browser Settings…” and at the bottom check “Require elevated trust when running outside the browser.”

If you’ve read other tutorials, you’ll see that a lot of them are using ComAutomationFactory. This is because these tutorials were written with a beta release of Silverlight 4. That’s why you’re getting this error:

The name 'ComAutomationFactory' does not exist in the current context

Change “ComAutomationFactory” to “AutomationFactory” and you’ll still notice that it won’t compile. Now you’re probably getting this error:

The name 'AutomationFactory' does not exist in the current context

ComAutomationFactory was previously in the namespace System.Windows.Interop. AutomationFactory has been moved to System.Runtime.InteropServices.Automation.

Now you might be getting the following error:

One or more types required to compile a dynamic expression cannot be found. Are you missing references to Microsoft.CSharp.dll and System.Core.dll

All you need to do is add a reference to “Microsoft.CSharp.dll.” You can do this by right-clicking “References” in the “Solution Explorer” and then navigating to the “.NET” tab and selecting it.

Are you a Git user? Let me help you make project management with Git simple. Checkout Gitpilot.

Follow me on Twitter: @jprichardson or read my blog on entrepreneurship: Techneur.

-JP

Using LINQ with CSV Files

In my last post, I wrote how you can convert a CSV to XML. I stated that the reason that you would do this is so that you can use LINQ on CSV files.

Using my CsvToXml class you can easily use LINQ on CSV. You can see the source for CsvToXml in my C# CommonLib library.

Let’s assume your CSV file looks like the following:

'Product','Price','DateStocked'
'Pepsi','4.50','2010-05-04'
'Coke','3.00','2010-09-22'
'Cheetos','7.25','2009-01-13'

You can then convert like so:

var csv2xml = new CsvToXml("mycsvfile.csv", true);
csv2xml.TextQualifier = '\'';
csv2xml.Convert();

You can then do cool LINQ queries using the “Records” property of the CsvToXml class:

var records = from rec in csv2xml.Records
			where (decimal)rec.Element("Price") > 3.5m
			orderby (string)rec.Element("Product")
			select rec;

or use LINQ like…

var records = from rec in csv2xml.Records
			where Convert.ToDateTime((string)rec.Element("DateStocked")) > new DateTime(2010, 9, 1)
			orderby (string)rec.Element("Product")
			select rec;

Then to access the data of the returned “records” object you can do this:

var myrec = records.ElementAt(0);
var firstProduct = (string)myrec.Element("Product");

Cool, huh? But we can do better. It’s annoying to cast your objects all of the time and to call Element(“CSV_COLUMN_NAME”). Why not take advantage of .NET 4.0 and use its ‘dynamic’ data type? Admittedly, I got this idea from this article: Creating Wrappers with DynamicObject.

Now we can use LINQ like this:

var records = from rec in csv2xml.DynamicRecords
			where (decimal)rec.Price > 3.5m
			orderby (string)rec.Product
			select rec;
			
//OR

records = from rec in csv2xml.DynamicRecords
		where Convert.ToDateTime((string)rec.DateStocked) > new DateTime(2010, 9, 1)
		orderby (string)rec.Product
		select rec;

A bit better. Now you don’t have to call “Element” method everytime, you still have to annoyingly cast. But, in each CSV column, we already know that the types should be consistent. Let’s define our types up front:

var csv2xml = new CsvToXml("mycsvfile.csv", true);
csv2xml.TextQualifier = '\'';

csv2xml.ColumnTypes.Add("Product", typeof(string));
csv2xml.ColumnTypes.Add("Price", typeof(decimal));
csv2xml.ColumnTypes.Add("DateStocked", typeof(DateTime));
			
csv2xml.Convert();

Now we can use LINQ like this:

records = from rec in csv2xml.DynamicRecords
			where rec.Price > 3.5m
			orderby rec.Product
			select rec;

//OR
records = from rec in csv2xml.DynamicRecords
			where rec.DateStocked > new DateTime(2010, 9, 1)
			orderby rec.Product
			select rec;

This is truly clean code. All you need to do is define your types for each column and you’re set. At this time, the major disadvantage is that you can’t lazily evaluate your CSV. You must read it entirely into memory. This class could be modified for lazy evaluation and then you would be able to use LINQ on large CSV files.

Are you a Git user? Let me help you make project management with Git simple. Checkout Gitpilot.

I hope you’ve enjoyed this. You should read my blog on entrepreneurship: Techneur or follow me on Twitter: @jprichardson.

-JP

Convert a CSV to XML using C#

I wrote a simple class that will help you convert a CSV file to XML. I can hear you asking “that’s nice JP, but why would I want to convert my simple CSV to that messy text format from the early 2000′s?” You would do this because you want to use LINQ with CSV files… that’s why.

Here is the crux of the algorithm:

public void Convert() {
	var tempLines = File.ReadAllLines(this.CsvFile);
	string[] lines = null;
	_columnNames = null;
	
	if (this.HasColumnNames) {
		_columnNames = Csv.RecordSplit(tempLines[0], this.RecordDelimiter, this.TextQualifier);
	
		lines = new string[tempLines.Length - 1];
		Array.Copy(tempLines, 1, lines, 0, lines.Length);
	} else {
		var columnCount = Csv.RecordSplit(tempLines[0], this.RecordDelimiter, this.TextQualifier).Length;
		_columnNames = new string[columnCount];
		for (int x = 0; x < _columnNames.Length; ++x)
			_columnNames[x] = "Column" + (x+1);
	
		lines = tempLines;
	}
	
	this.XmlData = new XElement("Records",
		from line in lines
		let fields = Csv.RecordSplit(line, this.RecordDelimiter, this.TextQualifier)
		select new XElement("Record",
			from fieldData in fields
			let i = fields.ToList().FindIndex(f => f == fieldData)
			select new XElement(_columnNames[i], fieldData)
		)
	);
}

You can look at the full code in my C# CommonLib library. As you can see, the code is really straightforward.

Let’s assume that your CSV file looks like this:

'Product','Price','DateStocked'
'Pepsi','4.50','2010-05-04'
'Coke','3.00','2010-09-22'
'Cheetos','7.25','2009-01-13'

You can then run Csv2Xml like this:

var csv = new CsvToXml(csvFile);
csv.RecordDelimiter = ','; 
csv.TextQualifier = '\'';
csv.HasColumnNames = false;
csv.Convert();

var actualXml = csv.XmlString;

The output XML will look like this:

<?xml version="1.0" encoding="utf-8"?>
<Records>
	<Record>
		<Column1>Pepsi</Column1>
		<Column2>4.50</Column2>
		<Column3>2010-05-04</Column3>
	</Record>
	<Record>
		<Column1>Coke</Column1>
		<Column2>3.00</Column2>
		<Column3>2010-09-22</Column3>
	</Record>
	<Record>
		<Column1>Cheetos</Column1>
		<Column2>7.25</Column2>
		<Column3>2009-01-13</Column3>
	</Record>
</Records>

If you set “HasColumnNames” to “True”, your XML output will look like this:

<?xml version="1.0" encoding="utf-8"?>
<Records>
	<Record>
		<Product>Pepsi</Product>
		<Price>4.50</Price>
		<DateStocked>2010-05-04</DateStocked>
	</Record>
	<Record>
		<Product>Coke</Product>
		<Price>3.00</Price>
		<DateStocked>2010-09-22</DateStocked>
	</Record>
	<Record>
		<Product>Cheetos</Product>
		<Price>7.25</Price>
		<DateStocked>2009-01-13</DateStocked>
	</Record>
</Records>

Are you a Git user? Let me help you make project management with Git simple. Checkout Gitpilot.

Pretty nifty huh? In the next post, I’ll explain how you can use this with LINQ. In the mean time follow me on Twitter (@jprichardson) or read my blog on entrepreneurship: Techneur.

-JP

LinkedList Always Slower than a List in C#?

I started using some LinkedList’s instead of Lists in some of my C# algorithms hoping to speed them up. However, I noticed that they just felt slower. Like any good developer, I figured that I should do due diligence and verify my feelings. So I decided to benchmark some simple loops. You can find the benchmark class here.

I thought that populating the collections with some random integers should be sufficient. I ran this code in Debug mode to avoid any compiler optimizations. Here is the code that I used:

var rand = new Random(Environment.TickCount);
var ll = new LinkedList<int>();
var list = new List<int>();
int count = 20000000;

BenchmarkTimer.Start("Linked List Insert");
for (int x = 0; x < count; ++x)
	ll.AddFirst(rand.Next(int.MaxValue));
BenchmarkTimer.StopAndOutput();

BenchmarkTimer.Start("List Insert");
for (int x = 0; x < count; ++x)
	list.Add(rand.Next(int.MaxValue));
BenchmarkTimer.StopAndOutput();

int y = 0;
BenchmarkTimer.Start("Linked List Iterate");
foreach (var i in ll)
	++y; //some atomic operation;
BenchmarkTimer.StopAndOutput();

int z = 0;
BenchmarkTimer.Start("List Iterate");
foreach (var i in list)
	++z; //some atomic operation;
BenchmarkTimer.StopAndOutput();

Here is the output:

Linked List Insert: 8959.808 ms
List Insert: 845.856 ms
Linked List Iterate: 203.632 ms
List Iterate: 125.312 ms

This result baffled me. A Linked List insert should be O(1) whereas as List Insert is Θ(1), O(n) (because of copy) if it needs to be resized. Both list iterations should be O(1) because of the enumerator. I looked at the disassembled output and it doesn’t shed much light on the situation.

Anyone else have any thoughts on why this is? Did I miss something glaringly obvious?

Are you a Git user? Let me help you make project management with Git simple. Checkout Gitpilot.

Read me blog on entrepreneurship: Techneur
Follow me on Twitter: @jprichardson

-JP

Three Ways to Sort a List of Objects with DateTime in C#

Here are three quick ways to sort a List of objects that have DateTime properties in C#.

Initial Code:

public class Person{
	public string Name;
	public DateTime Birthday;
}

var people = new List<Person>();
var p1 = new Person() { Name = "Leslie", Birthday = new DateTime(1983, 9, 4) };
var p2 = new Person() { Name = "Chris", Birthday = new DateTime(2001, 6, 19) };
var p3 = new Person() { Name = "JP", Birthday = new DateTime(1983, 4, 5) };

people.Add(p1); people.Add(p2); people.Add(p3);
people.ForEach(p => Console.WriteLine(p.Name));

You can see that the output is: “Leslie”, “Chris”, and then “JP”

Let’s start with the LINQ way:

var persons = from p in people
		    orderby p.Birthday
		    select p;
persons.ToList().ForEach(p => Console.WriteLine(p.Name));

You can see the correct output: “JP”, “Leslie”, and then “Chris”

Old fashion way using a Delegate:

people.Sort(delegate(Person ps1, Person ps2) { return DateTime.Compare(ps1.Birthday, ps2.Birthday); });
people.ForEach(p => Console.WriteLine(p.Name));

Cleaner way using a Lambda:

people.Sort((ps1, ps2) => DateTime.Compare(ps1.Birthday, ps2.Birthday));
people.ForEach(p => Console.WriteLine(p.Name));

Are you a Git user? Let me help you make project management with Git simple. Checkout Gitpilot.

Read me blog on entrepreneurship: Techneur
Follow me on Twitter: @jprichardson

-JP

C# yield

I’ve seen the “yield” keyword here and there… but I’ve never really cared to learn what it does. Until now. Actually, it’s my understanding that it’s more or less a convenience keyword like “var.”

In short, “yield” allows you to return an IEnumerable without creating a List.

Example: (old way)

IEnumerable<Car> FindBlueCars(){
	var blueCars = new List<Car>();
	foreach (var c in _allCars)
		if (c.IsBlue())
			blueCars.Add(c);
	return blueCars;
}

Example: (new way)

IEnumerable<Car> FindBlueCars(){
	foreach (var c in _allCars)
		if (c.IsBlue())
			yield return c;
}

Saves some typing. MSDN reference for yield.

Are you a Git user? Let me help you make project management with Git simple. Checkout Gitpilot.

Read me blog on entrepreneurship: Techneur
Follow me on Twitter: @jprichardson

-JP

Benchmarking C# Apps & Algorithms

I created a class to help me benchmark my C# algorithms.

Here’s how you can use it:

BenchmarkTimer.Start("Long running algorithm");
for (int x = 0; x < 3; ++x) {
	BenchmarkTimer.Start("Small running algorithm");
	Thread.Sleep(1000);
	BenchmarkTimer.StopAndOutput();
}
BenchmarkTimer.StopAndOutput();

Output:
Small running algorithm: 1000.3712 ms
Small running algorithm: 1000.3712 ms
Small running algorithm: 1000.3712 ms
Long running algorithm: 3001.1136 ms

You can find my entire C# CommonLib library here: http://github.com/jprichardson/CommonLib

Here is the relevant class:

public static class BenchmarkTimer
	{
		private static Stack<BenchmarkData> _startStack = new Stack<BenchmarkData>();

		public static void Start() {
			_startStack.Push(new BenchmarkData());
		}

		public static void Start(string label) {
			var bd = new BenchmarkData() { Label = label };
			_startStack.Push(bd);
		}

		public static TimeSpan Stop() {
			var stop = DateTime.Now;
			var startBD = _startStack.Pop();
			return stop - startBD.DateTime;
		}

		public static void StopAndOutput() {
			var stop = DateTime.Now;
			var startBD = _startStack.Pop();

			var delta = stop - startBD.DateTime;

			var lbl = "{0}: {1} ms";
			Console.WriteLine(String.Format(lbl, startBD.Label, delta.TotalMilliseconds));
		}

		private class BenchmarkData
		{
			public DateTime DateTime { get; set; }
			public string Label { get; set; }

			public BenchmarkData(){
				this.DateTime = DateTime.Now;
				this.Label = "";
			}
		}
	}

Are you a Git user? Let me help you make project management with Git simple. Checkout Gitpilot.

Follow me on Twitter: @jprichardson
Read my blog entrepreneurship: Techneur

-JP

Handy C# XML Serialization Methods

I have been hacking away on WPF/MVVM, GWT/MVP, and Rails for the last six months and haven’t found much time to update. But I wrote some handy C# XML serialization methods. They are very straightforward.

Deserialize:

public static T ReadFromFile(string file) {
	XmlSerializer xs = new XmlSerializer(typeof(T));
	StreamReader sr = new StreamReader(file);
	T ret = (T)xs.Deserialize(sr);
	sr.Close();
	return ret;
}

Serialize:

public static void WriteToFile(T obj, string file) {
	XmlSerializer xs = new XmlSerializer(typeof(T));
	StreamWriter sw = new StreamWriter(file);
	xs.Serialize(sw, obj);
	sw.Close();
}

Enjoy.

Are you a Git user? Let me help you make project management with Git simple. Checkout Gitpilot.

SQLite Bulk Insert In C#/.NET

Recently, I had a project where I needed to load 1 million+ records into a SQLite database. I downloaded the SQLite ADO.NET adapter and setup the Entity framework to map to my SQLite database. All was simple and all was well!

I started inserting the data into my database; lo and behold, it was taking forever! In fact, it took most of a full working day to insert my data. I knew something wasn’t right. A simple Google search pointed me to the SQLite FAQ. It turns out that SQLite wraps every INSERT into a transaction. Simple solution: start a transaction and perform multiple INSERTs. I needed my inserts to be fast, so I just had to write a class to encapsulate this.

It’s very simple to use. Make sure that your SQLite database file has been created and your table has been created as well.

Let’s assume your database schema looks like the following:
Id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
LastName VARCHAR(16) NOT NULL,
Height REAL NOT NULL

You would then use SQLiteBulkInsert as follows:

SQLiteBulkInsert sbi = new SQLiteBulkInsert(yourDatabaseConnectionObject, "yourTableName");
sbi.AddParameter("LastName", DbType.String);
sbi.AddParameter("Height", DbType.Single);

You can then insert records:
for (int x = 0; x &lt; 10000; x++)
	sbi.Insert(new object[]{someString, someFloat});
sbi.Flush();

That’s it! You should look at the file SQLiteBulkInsertTest.cs for more details.

SQLiteBulkInsert.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SQLite;
using System.Data;

namespace YourProject.Data.SQLite
{
	public class SQLiteBulkInsert
	{
		private SQLiteConnection m_dbCon;
		private SQLiteCommand m_cmd;
		private SQLiteTransaction m_trans;

		private Dictionary m_parameters = new Dictionary();

		private uint m_counter = 0;

		private string m_beginInsertText;

		public SQLiteBulkInsert(SQLiteConnection dbConnection, string tableName) {
			m_dbCon = dbConnection;
			m_tableName = tableName;

			StringBuilder query = new StringBuilder(255);
			query.Append("INSERT INTO ["); query.Append(tableName); query.Append("] (");
			m_beginInsertText = query.ToString();
		}

		private bool m_allowBulkInsert = true;
		public bool AllowBulkInsert { get { return m_allowBulkInsert; } set { m_allowBulkInsert = value; } }

		public string CommandText {
			get {
				if (m_parameters.Count &lt; 1)
					throw new SQLiteException("You must add at least one parameter.");

				StringBuilder sb = new StringBuilder(255);
				sb.Append(m_beginInsertText);

				foreach (string param in m_parameters.Keys) {
					sb.Append('[');
					sb.Append(param);
					sb.Append(']');
					sb.Append(", ");
				}
				sb.Remove(sb.Length - 2, 2);

				sb.Append(") VALUES (");

				foreach (string param in m_parameters.Keys) {
					sb.Append(m_paramDelim);
					sb.Append(param);
					sb.Append(", ");
				}
				sb.Remove(sb.Length - 2, 2);

				sb.Append(")");

				return sb.ToString();
			}
		}

		private uint m_commitMax = 10000;
		public uint CommitMax { get { return m_commitMax; } set { m_commitMax = value; } }

		private string m_tableName;
		public string TableName { get { return m_tableName; } }

		private string m_paramDelim = ":";
		public string ParamDelimiter { get { return m_paramDelim; } }

		public void AddParameter(string name, DbType dbType) {
			SQLiteParameter param = new SQLiteParameter(m_paramDelim + name, dbType);
			m_parameters.Add(name, param);
		}

		public void Flush() {
			try {
				if (m_trans != null)
					m_trans.Commit();
			}
			catch (Exception ex) { throw new Exception("Could not commit transaction. See InnerException for more details", ex); }
			finally {
				if (m_trans != null)
					m_trans.Dispose();

				m_trans = null;
				m_counter = 0;
			}
		}

		public void Insert(object[] paramValues) {
			if (paramValues.Length != m_parameters.Count)
				throw new Exception("The values array count must be equal to the count of the number of parameters.");

			m_counter++;

			if (m_counter == 1) {
				if (m_allowBulkInsert)
					m_trans = m_dbCon.BeginTransaction();

				m_cmd = m_dbCon.CreateCommand();
				foreach (SQLiteParameter par in m_parameters.Values)
					m_cmd.Parameters.Add(par);

				m_cmd.CommandText = this.CommandText;
			}

			int i = 0;
			foreach (SQLiteParameter par in m_parameters.Values) {
				par.Value = paramValues[i];
				i++;
			}

			m_cmd.ExecuteNonQuery();

			if (m_counter == m_commitMax) {
				try {
					if (m_trans != null)
						m_trans.Commit();
				}
				catch (Exception ex) { }
				finally {
					if (m_trans != null) {
						m_trans.Dispose();
						m_trans = null;
					}

					m_counter = 0;
				}
			}
		}
	}
}

SQLiteBulkInsertTest.cs:
using YourProject.Data.SQLite;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Data.SQLite;
using System.Data;
using System.IO;
using System;

namespace TestYourProject
{
    /// 
    ///This is a test class for SQLiteBulkInsertTest and is intended
    ///to contain all SQLiteBulkInsertTest Unit Tests
    ///
	[TestClass()]
	public class SQLiteBulkInsertTest
	{
		private static string m_testDir;
		private static string m_testFile;
		private static string m_testTableName = "test_table";
		private static string m_connectionString;

		private static string m_deleteAllQuery = "DELETE FROM [{0}]";
		private static string m_countAllQuery = "SELECT COUNT(id) FROM [{0}]";
		private static string m_selectAllQuery = "SELECT * FROM [{0}]";

		private static SQLiteConnection m_dbCon;
		private static SQLiteCommand m_deleteAllCmd;
		private static SQLiteCommand m_countAllCmd;
		private static SQLiteCommand m_selectAllCmd;

		private TestContext testContextInstance;

		/// 
		///Gets or sets the test context which provides
		///information about and functionality for the current test run.
		///
		public TestContext TestContext {
			get {
				return testContextInstance;
			}
			set {
				testContextInstance = value;
			}
		}

		#region Additional test attributes
		//
		//You can use the following additional attributes as you write your tests:
		//
		//Use ClassInitialize to run code before running the first test in the class
		[ClassInitialize()]
		public static void MyClassInitialize(TestContext testContext){
			Random rand = new Random(Environment.TickCount);
			int rn = rand.Next(0, int.MaxValue);
			m_testDir = @"C:\SqliteBulkInsertTest-" + rn + @"\";
			m_testFile = m_testDir + "db.sqlite";

			if (!Directory.Exists(m_testDir))
				Directory.CreateDirectory(m_testDir);

			if (!File.Exists(m_testFile)) {
				FileStream fs = File.Create(m_testFile);
				fs.Close();
			}

			m_connectionString = string.Format(@"data source={0};datetimeformat=Ticks", m_testFile);
			m_dbCon = new SQLiteConnection(m_connectionString);
			m_dbCon.Open();

			SQLiteCommand cmd = m_dbCon.CreateCommand();
			string query = "CREATE TABLE IF NOT EXISTS [{0}] (id INTEGER PRIMARY KEY AUTOINCREMENT, somestring VARCHAR(16), somereal REAL, someint INTEGER(4), somedt DATETIME)";
			query = string.Format(query, m_testTableName);
			cmd.CommandText = query;
			cmd.ExecuteNonQuery();
		}
		//
		//Use ClassCleanup to run code after all tests in a class have run
		[ClassCleanup()]
		public static void MyClassCleanup(){
			m_dbCon.Close();

			File.Delete(m_testFile);
			Directory.Delete(m_testDir);
		}
		#endregion

		private void AddParameters(SQLiteBulkInsert target) {
			target.AddParameter("somestring", DbType.String);
			target.AddParameter("somereal", DbType.String);
			target.AddParameter("someint", DbType.Int32);
			target.AddParameter("somedt", DbType.DateTime);
		}

		private long CountRecords() {
			m_countAllCmd = m_dbCon.CreateCommand();
			m_countAllCmd.CommandText = string.Format(m_countAllQuery, m_testTableName);

			long ret = (long)m_countAllCmd.ExecuteScalar();
			m_countAllCmd.Dispose();

			return ret;
		}

		private void DeleteRecords() {
			m_deleteAllCmd = m_dbCon.CreateCommand();
			m_deleteAllCmd.CommandText = string.Format(m_deleteAllQuery, m_testTableName);

			m_deleteAllCmd.ExecuteNonQuery();
			m_deleteAllCmd.Dispose();
		}

		private SQLiteDataReader SelectAllRecords() {
			m_selectAllCmd = m_dbCon.CreateCommand();
			m_selectAllCmd.CommandText = string.Format(m_selectAllQuery, m_testTableName);
			return m_selectAllCmd.ExecuteReader();
		}

		[TestMethod()]
		public void AddParameterTest() {
			SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);
			AddParameters(target);

			string pd = target.ParamDelimiter;
			string expectedStmnt = "INSERT INTO [{0}] ([somestring], [somereal], [someint], [somedt]) VALUES ({1}somestring, {2}somereal, {3}someint, {4}somedt)";
			expectedStmnt = string.Format(expectedStmnt, m_testTableName, pd, pd, pd, pd);
			Assert.AreEqual(expectedStmnt, target.CommandText);
		}

		[TestMethod()]
		public void SQLiteBulkInsertConstructorTest() {
			SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);
			Assert.AreEqual(m_testTableName, target.TableName);

			bool wasException = false;
			try {
				string a = target.CommandText;
			}
			catch (SQLiteException ex) { wasException = true; }

			Assert.IsTrue(wasException);
		}

		[TestMethod()]
		public void CommandTextTest() {
			SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);
			AddParameters(target);

			string pd = target.ParamDelimiter;
			string expectedStmnt = "INSERT INTO [{0}] ([somestring], [somereal], [someint], [somedt]) VALUES ({1}somestring, {2}somereal, {3}someint, {4}somedt)";
			expectedStmnt = string.Format(expectedStmnt, m_testTableName, pd, pd, pd, pd);
			Assert.AreEqual(expectedStmnt, target.CommandText);
		}

		[TestMethod()]
		public void TableNameTest() {
			SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);
			Assert.AreEqual(m_testTableName, target.TableName);
		}

		[TestMethod()]
		public void InsertTest() {
			SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);

			bool didThrow = false;
			try {
				target.Insert(new object[] { "hello" }); //object.length must equal the number of parameters added
			}
			catch (Exception ex) { didThrow = true; }
			Assert.IsTrue(didThrow);

			AddParameters(target);

			target.CommitMax = 4;
			DateTime dt1 = DateTime.Now; DateTime dt2 = DateTime.Now; DateTime dt3 = DateTime.Now; DateTime dt4 = DateTime.Now;
			target.Insert(new object[] { "john", 3.45f, 10, dt1 });
			target.Insert(new object[] { "paul", -0.34f, 100, dt2 });
			target.Insert(new object[] { "ringo", 1000.98f, 1000, dt3 });
			target.Insert(new object[] { "george", 5.0f, 10000, dt4 });

			long count = CountRecords();
			Assert.AreEqual(4, count);

			SQLiteDataReader reader = SelectAllRecords();

			Assert.IsTrue(reader.Read());
			Assert.AreEqual("john", reader.GetString(1)); Assert.AreEqual(3.45f, reader.GetFloat(2));
			Assert.AreEqual(10, reader.GetInt32(3)); Assert.AreEqual(dt1, reader.GetDateTime(4));

			Assert.IsTrue(reader.Read());
			Assert.AreEqual("paul", reader.GetString(1)); Assert.AreEqual(-0.34f, reader.GetFloat(2));
			Assert.AreEqual(100, reader.GetInt32(3)); Assert.AreEqual(dt2, reader.GetDateTime(4));

			Assert.IsTrue(reader.Read());
			Assert.AreEqual("ringo", reader.GetString(1)); Assert.AreEqual(1000.98f, reader.GetFloat(2));
			Assert.AreEqual(1000, reader.GetInt32(3)); Assert.AreEqual(dt3, reader.GetDateTime(4));

			Assert.IsTrue(reader.Read());
			Assert.AreEqual("george", reader.GetString(1)); Assert.AreEqual(5.0f, reader.GetFloat(2));
			Assert.AreEqual(10000, reader.GetInt32(3)); Assert.AreEqual(dt4, reader.GetDateTime(4));

			Assert.IsFalse(reader.Read());

			DeleteRecords();

			count = CountRecords();
			Assert.AreEqual(0, count);
		}

		[TestMethod()]
		public void FlushTest() {
			string[] names = new string[] { "metalica", "beatles", "coldplay", "tiesto", "t-pain", "blink 182", "plain white ts", "staind", "pink floyd" };
			Random rand = new Random(Environment.TickCount);

			SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);
			AddParameters(target);

			target.CommitMax = 1000;

			//Insert less records than commitmax
			for (int x = 0; x &lt; 50; x++)
				target.Insert(new object[] { names[rand.Next(names.Length)], (float)rand.NextDouble(), rand.Next(50), DateTime.Now });

			//Close connect to verify records were not inserted
			m_dbCon.Close();

			m_dbCon = new SQLiteConnection(m_connectionString);
			m_dbCon.Open();

			long count = CountRecords();
			Assert.AreEqual(0, count);

			//Now actually verify flush worked
			target = new SQLiteBulkInsert(m_dbCon, m_testTableName);
			AddParameters(target);

			target.CommitMax = 1000;

			//Insert less records than commitmax
			for (int x = 0; x &lt; 50; x++)
				target.Insert(new object[] { names[rand.Next(names.Length)], (float)rand.NextDouble(), rand.Next(50), DateTime.Now });

			target.Flush();

			count = CountRecords();
			Assert.AreEqual(50, count);

			//Close connect to verify flush worked
			m_dbCon.Close();

			m_dbCon = new SQLiteConnection(m_connectionString);
			m_dbCon.Open();

			count = CountRecords();
			Assert.AreEqual(50, count);

			DeleteRecords();
			count = CountRecords();
			Assert.AreEqual(0, count);
		}

		[TestMethod()]
		public void CommitMaxTest() {
			SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);

			target.CommitMax = 4;
			Assert.AreEqual(4, target.CommitMax);

			target.CommitMax = 1000;
			Assert.AreEqual(1000, target.CommitMax);
		}

		//SPEED TEST
		[TestMethod()]
		public void AllowBulkInsertTest() {
			string[] names = new string[] { "metalica", "beatles", "coldplay", "tiesto", "t-pain", "blink 182", "plain white ts", "staind", "pink floyd"};
			Random rand = new Random(Environment.TickCount);

			SQLiteBulkInsert target = new SQLiteBulkInsert(m_dbCon, m_testTableName);
			AddParameters(target);

			const int COUNT = 100;

			target.CommitMax = COUNT;

			DateTime start1 = DateTime.Now;
			for (int x = 0; x &lt; COUNT; x++)
				target.Insert(new object[] { names[rand.Next(names.Length)], (float)rand.NextDouble(), rand.Next(COUNT), DateTime.Now });

			DateTime end1 = DateTime.Now;
			TimeSpan delta1 = end1 - start1;

			DeleteRecords();

			target.AllowBulkInsert = false;
			DateTime start2 = DateTime.Now;
			for (int x = 0; x &lt; COUNT; x++)
				target.Insert(new object[] { names[rand.Next(names.Length)], (float)rand.NextDouble(), rand.Next(COUNT), DateTime.Now });

			DateTime end2 = DateTime.Now;
			TimeSpan delta2 = end2 - start2;

			//THIS MAY FAIL DEPENDING UPON THE MACHINE THE TEST IS RUNNING ON.
			Assert.IsTrue(delta1.TotalSeconds &lt; 0.1); //approx true for 100 recs 			Assert.IsTrue(delta2.TotalSeconds &gt; 1.0); //approx true for 100 recs;

			//UNCOMMENT THIS TO MAKE IT FAIL AND SEE ACTUAL NUMBERS IN FAILED REPORT
			//Assert.AreEqual(delta1.TotalSeconds, delta2.TotalSeconds);

			DeleteRecords();
		}
	}
}

Enjoy. Drop me a line if you have any problems.

Are you a Git user? Let me help you make project management with Git simple. Checkout Gitpilot.

Read my blog on entrepreneurship: Techneur
Follow me on Twitter: @jprichardson

-JP

Follow

Get every new post delivered to your Inbox.