Thargy.com

Blogs and articles by Craig Dean

Simulating SQL Server (Part 2)

If you haven’t read it already, why not start with Simulating SQL Server (Part 1)?

Last time we looked at creating a RecordSetDefinition  as a collection of ColumnDefinition s.  Once we had a way of defining what a recordset looks like, we then were able to create an ObjectRecord .

The next step is to collect those ObectRecord s together into a record set implementation.  For this I’ve added and ObjectSet .

image

 

Interestingly the BCL don’t expose a ‘recordset’ independently, instead, it’s always accessed through an IDataReader .  We want to be able to manipulate our recordsets though, so I added an IObjectSet  interface, and we can use the collection operators to make our sets, including Collection Initializers.

// We can stick these records into a recordset
// Note the records must have the same RecordSetDefinition (unless it's an exception record)
// The final record will through an exception when reached!
ObjectSet recordSet = new ObjectSet(recordSetDefinition)
{
new ObjectRecord(recordSetDefinition, 1, "Test", "This is my test record"),
new ObjectRecord(recordSetDefinition, true)
};

As we can create records independently of a record set (again great for testing a method that accepts an IDataRecord) then we need to be sure that any records we add to our record set implement the same RecordSetDefinition .  The above example adds two records, the second of which is random, of course, we can add and remove objects as we like –

// Create a random record and add it.
IObjectRecord randomRecord = new ObjectRecord(recordSetDefinition, true);
recordSet.Add(randomRecord);

// And remove it again.
recordSet.Remove(randomRecord);

But in many cases, we may not care about the actual data (or we want to test how well it copes with random data).  We already showed how to create a random record, so what we need is a fully random record set, and thankfully I added one of them too-

RandomSet randomSet = new RandomSet(recordSetDefinition);

Couldn’t be easier – using the information from the recordSetDefinition  this creates between 0 and 1000 rows of random data.  There are lots of useful optional parameters here as well –

/// <summary>
/// Initializes a new instance of the <see cref="RandomSet" /> class.
/// </summary>
/// <param name="recordSetDefinition">The record set definition.</param>
/// <param name="minRows">The min rows.</param>
/// <param name="maxRows">The max rows.</param>
/// <param name="nullProbability">The probability of a column's value being set to SQL null (0.0 for no nulls) [Defaults to 0.1 = 10%].</param>
/// <param name="columnGenerators">The column generators is an array of functions that generate a value for each column, if the function is
/// <see langword="null"/> for a particular index then a random value is generated, if it is not null then the function is used. The function takes
/// the current row number as it's only parameter and must return an object of the correct type for the column.</param>
/// <exception cref="System.ArgumentOutOfRangeException"></exception>
/// <remarks></remarks>
public RandomSet([NotNull] RecordSetDefinition recordSetDefinition, int minRows = 0, int maxRows = 1000,
double nullProbability = 0.1,
Func<int, object>[] columnGenerators = null)

Which compares quite favourably to the full signature for creating a record –

/// <summary>
/// Initializes a new instance of the <see cref="ObjectRecord" /> class.
/// </summary>
/// <param name="recordSetDefinition">The table definition.</param>
/// <param name="randomData">if set to <see langword="true" /> fills columns with random data; otherwise fills them with their default values.</param>
/// <param name="nullProbability">The probability of a column's value being set to SQL null (0.0 for no nulls) -
/// this is only applicable is <see paramref="randomData" /> is set to <see langword="true" /> [Defaults to 0.1 = 10%].</param>
/// <param name="columnGenerators">The column generators is an array of functions that generate a value for each column, if the function is
/// <see langword="null" /> for a particular index then a random value is generated, if it is not null then the function is used. The function takes
/// the current row number as it's only parameter and must return an object of the correct type for the column.</param>
/// <param name="rowNumber">The optional row number to pass to the generator.</param>
/// <exception cref="System.ArgumentException">Thrown if the number of column generators exceeds the number of columns in the record set definition.</exception>
/// <remarks></remarks>
public ObjectRecord([NotNull] RecordSetDefinition recordSetDefinition, bool randomData = false,
double nullProbability = 0.1,
Func<int, object>[] columnGenerators = null, int rowNumber = 1)

Two useful parameters here include nullProbability  which determines how likely nulls will appear in columns (that are defined as allowing nulls) and columnGenerators  which allow a function to optionally be specified for columns that you want to generate specific values for (e.g. lookup out of an array). An example is shown below.

Now we have a recordset, it’s only used in an IDataReader , so for this, we create another Collection, ObjectReader  which implements IDataReader  as well as ICollection<IObjectSet> . Again we can use Collection Initializers

// We can add recordsets to an ObjectReader
ObjectReader reader = new ObjectReader
{
recordSet
};

We now have a reader with one record set in it but more, can be added, e.g. a random set –

// We can also fix certain rows values using the column generators arry, a null indicates
// that the column should us a random value, otherwise a lambda can be supplied - in this case
// it sets the row to the row number (1 - indexed).
reader.Add(new RandomSet(recordSetDefinition,
columnGenerators: new Func<int, object>[] {null, row => "Row #" + row}));

And finally, we can use this reader just like a normal data reader –

// Now that we have a reader we can use it like a normal reader - it even simulates disposal.
using (IDataReader dataReader = reader)
{
int recordset = 1;
do
{
Trace.Write("Recordset #" + recordset);
int rows = 0;
while (dataReader.Read())
rows++;
Trace.WriteLine(" - " + rows + " rows.");
recordset++;
} while (dataReader.NextResult());
}

Again the full project can be viewed on GitHub or use the download link below to see the complete project.  Next time we will look in more detail at the other functions of the ObjectReader, and also we’ll attempt to simulate realistic SQL Server errors.

Related Images:

, , , , , ,

Comments are currently closed.

2 thoughts on “Simulating SQL Server (Part 2)