/// <summary>
		/// Writes a profiling dataset to the database.
		/// </summary>
		public void WriteDataSet(IProfilingDataSet dataSet)
		{
			if (dataSet == null)
				throw new ArgumentNullException("dataSet");
			
			using (SQLiteTransaction transaction = this.connection.BeginTransaction()) {
				SQLiteCommand cmd = this.connection.CreateCommand();
				
				if (dataSetCount == -1)
					dataSetCount = 0;
				
				cmd.Parameters.Add(new SQLiteParameter("id", dataSetCount));
				cmd.Parameters.Add(new SQLiteParameter("isfirst", dataSet.IsFirst));
				cmd.Parameters.Add(new SQLiteParameter("rootid", functionInfoCount));
				
				cmd.CommandText = "INSERT INTO DataSets(id, isfirst, rootid)" +
					"VALUES(?,?,?);";
				
				int dataSetStartId = functionInfoCount;
				
				using (SQLiteCommand loopCommand = this.connection.CreateCommand()) {
					CallTreeNode node = dataSet.RootNode;
					
					loopCommand.CommandText = "INSERT INTO Calls(id, endid, parentid, nameid, cpucyclesspent, cpucyclesspentself, isactiveatstart, callcount)" +
						"VALUES(?,?,?,?,?,?,?,?);";
					
					CallsParams dataParams = new CallsParams();
					loopCommand.Parameters.Add(dataParams.functionInfoId = new SQLiteParameter());
					loopCommand.Parameters.Add(dataParams.endId = new SQLiteParameter());
					loopCommand.Parameters.Add(dataParams.parentId = new SQLiteParameter());
					loopCommand.Parameters.Add(dataParams.nameId = new SQLiteParameter());
					loopCommand.Parameters.Add(dataParams.cpuCyclesSpent = new SQLiteParameter());
					loopCommand.Parameters.Add(dataParams.cpuCyclesSpentSelf = new SQLiteParameter());
					loopCommand.Parameters.Add(dataParams.isActiveAtStart = new SQLiteParameter());
					loopCommand.Parameters.Add(dataParams.callCount = new SQLiteParameter());

					InsertCalls(loopCommand, node, -1, dataParams);
				}
				
				using (SQLiteCommand functionsCommand = this.connection.CreateCommand()) {
					functionsCommand.CommandText = string.Format(@"
						INSERT INTO Functions
						SELECT {0}, nameid, SUM(cpucyclesspent), SUM(cpucyclesspentself), SUM(isactiveatstart), SUM(callcount), MAX(id != endid)
	 					FROM Calls
	 					WHERE id BETWEEN {1} AND {2}
	 					GROUP BY nameid;", dataSetCount, dataSetStartId, functionInfoCount - 1);
					
					functionsCommand.ExecuteNonQuery();
				}
				
				cmd.ExecuteNonQuery();
				dataSetCount++;
				
				transaction.Commit();
			}
		}
		void InsertCalls(SQLiteCommand cmd, CallTreeNode node, int parentId, CallsParams dataParams)
		{
			int thisID = functionInfoCount++;
			
			foreach (CallTreeNode child in node.Children) {
				InsertCalls(cmd, child, thisID, dataParams);
			}
			
			long cpuCycles = node.CpuCyclesSpent;
			long cpuCyclesSelf = node.CpuCyclesSpentSelf;
			
			// we sometimes saw invalid data with the 0x0080000000000000L bit set
			if (cpuCycles > 0x0007ffffffffffffL || cpuCycles < 0) {
				throw new InvalidOperationException("Too large CpuCyclesSpent - there's something wrong in the data");
			}
			
			if (node.NameMapping.Id != 0 && (cpuCyclesSelf > cpuCycles || cpuCyclesSelf < 0)) {
				throw new InvalidOperationException("Too large/small CpuCyclesSpentSelf (" + cpuCyclesSelf + ") - there's something wrong in the data");
			}
			
			dataParams.callCount.Value = node.RawCallCount;
			dataParams.isActiveAtStart.Value = node.IsActiveAtStart;
			dataParams.cpuCyclesSpent.Value = cpuCycles;
			dataParams.cpuCyclesSpentSelf.Value = cpuCyclesSelf;

			dataParams.functionInfoId.Value = thisID;
			dataParams.nameId.Value = node.NameMapping.Id;
			dataParams.parentId.Value = parentId;
			dataParams.endId.Value = functionInfoCount - 1;
			
			cmd.ExecuteNonQuery();
		}