Walkthrough: Creating a Custom Data Generator that Aggregates Standard Generators

In Visual Studio Team System Database Edition, you can create instances of the standard data generator classes in your custom data generator classes. By taking this approach, you can conveniently reduce the amount of logic you have to include in your custom data generators. For example, you might want to create a generator that generates random string data that matches more than one complex pattern. You can create a custom data generator that contains the logic to handle the multiple patterns, and you can use the standard RegularExpression generator to handle the complex pattern matching.

In this walkthrough, you create a custom data generator that aggregates the standard DateTime generator. You create a generator that generates data that is in one of two distinct date ranges. The generator accepts two distinct ranges as the input and generates a random date that is in one of the two ranges.

Note

For more information about the goal of this custom data generator and to learn how to accomplish the same goal by using normal extensibility, see Walkthrough: Creating a Custom Data Generator for a Check Constraint.

In this walkthrough, you perform the following tasks:

  • Create a class that inherits from Generator.

  • Create input properties so that the user can specify the two date ranges.

  • Create an output property to use as the generator output.

  • Create two instances of the standard DateTime generator to represent each of the two possible ranges.

  • Override Generator methods and, within them, delegate the work to the standard generators.

  • Sign the generator with a strong name.

Prerequisites

To complete this walkthrough, you need the following:

  • Database Edition

Creating the Custom Data Generator Class

To create the custom data generator class

  1. In Visual Studio, create a Class Library project in the language of your choice, and name it GeneratorDateRanges2.

  2. On the Project menu, click Add Reference.

    The Add Reference dialog box appears.

  3. Click the .NET tab. In the Component Name list, click Microsoft.VisualStudio.TeamSystem.Data, and then click OK.

  4. On the Project menu, click Add Reference.

    The Add Reference dialog box appears.

  5. Click the Browse tab, and browse to the location ...\Program Files\Microsoft Visual Studio 9.0\DBPro\Extensions.

  6. Click Microsoft.VisualStudio.TeamSystem.Data.Generators.dll, and then click OK.

  7. (Optional, Visual Basic only) In Solution Explorer, click Show All Files, and expand the References node to verify the new references.

  8. At the top of the Code window, before the class declaration, add the following line of code:

    Imports Microsoft.VisualStudio.TeamSystem.Data.DataGenerator
    Imports Microsoft.VisualStudio.TeamSystem.Data.Generators
    Imports System.Data.SqlTypes
    
    using Microsoft.VisualStudio.TeamSystem.Data.DataGenerator;
    using Microsoft.VisualStudio.TeamSystem.Data.Generators;
    using System.Data.SqlTypes;
    
  9. Rename the class from Class1 to GeneratorDateRanges2, and specify that your class inherits from Generator, as shown in the following example.

    Warning

    By default, the name that you give your class is the name that appears in the list in the Generator column in the Column Details window. You should specify a name that does not conflict with the name of a standard generator or of another custom generator.

    Public Class GeneratorDateRanges2
        Inherits Generator
    
    End Class
    
    public class GeneratorDateRanges2: Generator
    {
    }
    
  10. On the File menu, click Save All.

Adding the Input Properties

This custom data generator accepts two date ranges as the input. To specify each range, the user specifies the minimum and the maximum date for each. Therefore, you must create four input properties in total: two minimum dates and two maximum dates.

To add the input properties

  1. Create four member variables to hold the minimum and maximum dates for the two date ranges, as shown in the following example.

    Dim range1MinValue As SqlDateTime
    Dim range1MaxValue As SqlDateTime
    
    Dim range2MinValue As SqlDateTime
    Dim range2MaxValue As SqlDateTime
    
    SqlDateTime range1MinValue;
    SqlDateTime range1MaxValue;
    
    SqlDateTime range2MinValue;
    SqlDateTime range2MaxValue;
    
  2. Create four properties to set the minimum and maximum dates for the two date ranges, as shown in the following example. The properties must have the InputAttribute to identify them as input properties.

    <Input(Visible:= true, TypeConverter:= GetType(SqlDateTimeConverter))> _
    Public Property Range1Min() As SqlDateTime
        Set(ByVal value As SqlDateTime)
            range1MinValue = value
        End Set
        Get
            Return range1MinValue
        End Get
    End Property
    
    <Input(Visible:= true, TypeConverter:= GetType(SqlDateTimeConverter))> _
    Public Property Range1Max() As SqlDateTime
        Set(ByVal value As SqlDateTime)
            range1MaxValue = value
        End Set
        Get
            Return range1MaxValue
        End Get
    End Property
    
    <Input(Visible:= true, TypeConverter:= GetType(SqlDateTimeConverter))> _
    Public Property Range2Min() As SqlDateTime
        Set(ByVal value As SqlDateTime)
            range2MinValue = value
        End Set
        Get
            Return range2MinValue
        End Get
    End Property
    
    <Input(Visible:= true, TypeConverter:= GetType(SqlDateTimeConverter))> _
    Public Property Range2Max() As SqlDateTime
        Set(ByVal value As SqlDateTime)
            range2MaxValue = value
        End Set
        Get
            Return range2MaxValue
        End Get
    End Property
    
    [Input(Visible = true, TypeConverter = typeof(SqlDateTimeConverter))]
    public SqlDateTime Range1Min
    {
        set {range1MinValue = value;}
        get {return range1MinValue;}
    }
    
    [Input(Visible = true, TypeConverter = typeof(SqlDateTimeConverter))]
    public SqlDateTime Range1Max
    {
        set {range1MaxValue = value;}
        get {return range1MaxValue;}
    }
    
    [Input(Visible = true, TypeConverter = typeof(SqlDateTimeConverter))]
    public SqlDateTime Range2Min
    {
        set {range2MinValue = value;}
        get {return range2MinValue;}
    }
    
    [Input(Visible = true, TypeConverter = typeof(SqlDateTimeConverter))]
    public SqlDateTime Range2Max
    {
        set {range2MaxValue = value;}
        get {return range2MaxValue;}
    }
    
  3. On the File menu, click Save All.

Adding the Output Property

This custom data generator returns one random date as the output. Therefore, you must create one output property.

To add the output property

  1. Create a member variable to hold the random date that is the output, as shown in the following example.

    Dim randomDateValue As SqlDateTime
    
    SqlDateTime randomDateValue;
    
  2. Create a property to return the random date as the output, as shown in the following example. The property must have the OutputAttribute to identify it as an output property.

    <Output()> _
    Public ReadOnly Property RandomDate() As SqlDateTime
        Get
            Return randomDateValue
        End Get
    End Property
    
    [Output]
    public SqlDateTime RandomDate
    {
        get {return randomDateValue;}
    }
    
  3. On the File menu, click Save All.

Overriding the OnInitialize Method

To override the OnInitialize method

  1. Create a member variable to generate random numbers, as shown in the following example. This variable randomly chooses between the two possible date ranges.

    Dim randomRange As Random
    
    Random randomRange;
    
  2. Create and instantiate two member variables that are the standard DateTime generators, as shown in the following example.

    Dim range1 As DatabaseDateTime = New DatabaseDateTime()
    Dim range2 As DatabaseDateTime = New DatabaseDateTime()
    
    DatabaseDateTime range1 = new DatabaseDateTime();
    DatabaseDateTime range2 = new DatabaseDateTime();
    
  3. Override the OnInitialize method, as shown in the following example. In this method, you seed the Random object and make your generator deterministic. You also call the Initialize method of the standard generators.

    Protected Overrides Sub OnInitialize(ByVal initInfo As GeneratorInit)
    
        randomRange = New Random(Me.Seed)  'deterministic
    
        range1.Initialize(initInfo)
        range2.Initialize(initInfo)
    
        MyBase.OnInitialize(initInfo)
    End Sub
    
    protected override void OnInitialize(GeneratorInit initInfo)
    {
        randomRange = new Random(this.Seed);  //deterministic
    
        range1.Initialize(initInfo);
        range2.Initialize(initInfo);
    
        base.OnInitialize(initInfo);
    }
    
  4. On the File menu, click Save All.

Overriding Other Methods

To override other methods

  1. Override OnSetInputValues, as shown in the following example. The inputs parameter to this method is an IDictionary of all the standard generator properties that the user set, such as Seed and Percentage Null. You call the SetInputValues methods of the standard generators to pass those values to them. You then set the Min and Max properties of each standard generator with the custom input properties that you created in this data generator.

    Protected Overrides Sub OnSetInputValues(ByVal inputs As IDictionary(Of String, Object))
    
        'It is important to call MyBase.OnSetInputValues first to get the inputs
        'from the Properties window first.
        '--------------------------------------------------------------------------
        MyBase.OnSetInputValues(inputs)
    
        range1.SetInputValues(inputs)
        range2.SetInputValues(inputs)
    
        range1.Min = range1MinValue
        range1.Max = range1MaxValue
        range2.Min = range2MinValue
        range2.Max = range2MaxValue
    
        range1.Distribution = New Uniform()
        range2.Distribution = New Uniform()
    End Sub
    
    protected override void OnSetInputValues(IDictionary<string, object> inputs)
    {
        //It is important to call base.OnSetInputValues first to get the inputs
        //from the Properties window first.
        //-------------------------------------------------------------------------
        base.OnSetInputValues(inputs);
    
        range1.SetInputValues(inputs);
        range2.SetInputValues(inputs);
    
        range1.Min = range1MinValue;
        range1.Max = range1MaxValue;
        range2.Min = range2MinValue;
        range2.Max = range2MaxValue;
    
        range1.Distribution = new Uniform();
        range2.Distribution = new Uniform();
    }
    
  2. Override OnValidateInputs to validate the inputs, as shown in the following example.

    Protected Overrides Sub OnValidateInputs()
    
        range1.ValidateInputs()
        range2.ValidateInputs()
    
        MyBase.OnValidateInputs()
    End Sub
    
    protected override void OnValidateInputs()
    {
        range1.ValidateInputs();
        range2.ValidateInputs();
    
        base.OnValidateInputs();
    }
    
  3. Override the Dispose(Boolean) method to clean up the standard generators, as shown in the following example.

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
    
        range1.Dispose()
        range2.Dispose()
    
        MyBase.Dispose(disposing)
    End Sub
    
    protected override void Dispose(bool disposing)
    {
        range1.Dispose();
        range2.Dispose();
    
        base.Dispose(disposing);
    }
    
  4. On the File menu, click Save All.

Overriding the OnGenerateNextValues Method

Database Edition calls the OnGenerateNextValues method of the generator to create the data that it needs. You must override this method to provide the logic that generates the random date for your output property. In this walkthrough, you delegate the responsibility for generating the random date to the standard DateTime generator.

To override the OnGenerateNextValues method

  1. Override the OnGenerateNextValues method, as shown in the following example.

    Protected Overrides Sub OnGenerateNextValues()
    
        'Generate a random date from either range 1 or range 2.
        'Randomly select either range 1 or range 2 by randomly 
        'generating an odd or an even random number.
        '------------------------------------------------------------
        If (randomRange.Next() Mod 2 = 0) Then  'check for odd or even
    
            'the standard generator does the work
            range1.GenerateNextValues()
            randomDateValue = range1.Result.Value
        Else
            'the standard generator does the work
            range2.GenerateNextValues()
            randomDateValue = range2.Result.Value
        End If
    
        MyBase.OnGenerateNextValues()
    End Sub
    
    protected override void OnGenerateNextValues()
    {
        //Generate a random date from either range 1 or range 2.
        //Randomly select either range 1 or range 2 by randomly 
        //generating an odd or an even random number.
        //------------------------------------------------------------
        if (randomRange.Next() % 2 == 0)  //check for odd or even
        {
            //the standard generator does the work
            range1.GenerateNextValues();
            randomDateValue = range1.Result.Value;
        }
        else
        {
            //the standard generator does the work
            range2.GenerateNextValues();
            randomDateValue = range2.Result.Value;
        }
    
        base.OnGenerateNextValues();
    }
    
  2. On the File menu, click Save All.

Defining the Type Converter

To specify the input properties for this data generator in the Properties window, you must provide a type converter that converts the input values to and from the SqlDateTime type.

To create the SqlDateTime type converter class

  1. On the Project menu, click Add Class.

    The Add New Item dialog box appears.

  2. In Name, type SqlDateTimeConverter.

  3. At the top of the Code window, before the class declaration, add the following lines of code:

    Imports System.ComponentModel
    Imports System.Data.SqlTypes
    Imports System.Globalization
    
    using System.ComponentModel;
    using System.Data.SqlTypes;
    using System.Globalization;
    
  4. Rename the class from Class1 to GeneratorDateRanges, and specify that your class inherits from TypeConverter.

    Public Class SqlDateTimeConverter
        Inherits TypeConverter
    
    End Class
    
    public class SqlDateTimeConverter: TypeConverter
    {
    }
    
  5. Within the class declaration, add the class constructor. If you are writing the type converter class in Visual Basic, skip to step 6.

    public SqlDateTimeConverter()
    {
    }
    
  6. Following the class constructor, add a method that verifies whether this type converter can perform a particular conversion.

    Public Overrides Function CanConvertFrom(ByVal context As ITypeDescriptorContext, ByVal sourceType As Type) As Boolean
        Dim result As Boolean
        result = False
        If (sourceType Is GetType(System.String)) Then
            result = True
        Else
            result = MyBase.CanConvertFrom(context, sourceType)
        End If
        Return result
    End Function 
    
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        bool result = false;
        if (sourceType == typeof(string))
        {
            result = true;
        }
        else
        {
            result = base.CanConvertFrom(context, sourceType);
        }
        return result;
    }
    
  7. Finally, add the converter methods.

    Public Overrides Function ConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object) As Object
        Dim dateTimeString As String
        dateTimeString = value.ToString
        If (dateTimeString.Length > 0) Then
            Dim dateTime As Date
            dateTime = Date.Parse(dateTimeString, culture)
            Return New SqlDateTime(dateTime)
        End If
        Return MyBase.ConvertFrom(context, culture, value)
    End Function
    
    Public Overrides Function CanConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal destinationType As System.Type) As Boolean
        If (destinationType Is GetType(System.String)) Then
            Return True
        End If
        Return MyBase.CanConvertTo(context, destinationType)
    End Function
    
    Public Overrides Function ConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object, ByVal destinationType As System.Type) As Object
        If (destinationType Is GetType(System.String)) Then
            Dim dateTime As Date
            dateTime = CType(value, SqlDateTime).Value
            dateTime.ToString(culture)
        End If
        Return MyBase.ConvertTo(context, culture, value, destinationType)
    End Function 
    
            public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
            {
                string dateTimeString = value as string;
                if (dateTimeString != null)
                {
                    DateTime dateTime = DateTime.Parse(dateTimeString, culture);
                    return new SqlDateTime(dateTime);
                }
                return base.ConvertFrom(context, culture, value);
            }
    
            public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
            {
                if (destinationType == typeof(string))
                {
                    return true;
                }
                return base.CanConvertTo(context, destinationType);
            }
    
            public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
            {
                if (destinationType == typeof(string))
                {
                    DateTime dateTime = ((SqlDateTime)value).Value;
                    dateTime.ToString(culture);
                }
                return base.ConvertTo(context, culture, value, destinationType);
            }
    
  8. On the File menu, click Save All.

Signing the Generator

All custom data generators must be signed with a strong name before they are registered.

To sign the generator with a strong name

  1. On the Project menu, click GeneratorDateRanges2 Properties to open the project properties.

  2. On the Signing tab, select the Sign the assembly check box.

  3. In the Choose a strong name key file box, click <New...>.

  4. In the Key file name box, type GeneratorDateRanges2Key, type and confirm a password, and then click OK.

    When you build your solution, the key file is used to sign the assembly.

  5. On the File menu, click Save All.

  6. On the Build menu, click Build Solution.

    Your data generator has now been built. Next, you must register it on your computer so that you can use it in data generation plans.

Security

For more information, see Security of Data Generators.

Next Steps

Now that you have built your data generator, you must register it on your computer. For more information, see one of the following:

See Also

Tasks

Walkthrough: Deploying a Custom Data Generator

Concepts

An Overview of Data Generator Extensibility

Reference

Microsoft.VisualStudio.TeamSystem.Data.DataGenerator

Other Resources

Creating Custom Data Generators

Using Standard Data Generators

Data Generator Walkthroughs