Created my first custom data generator for VSTE DBPro (aka DataDude)

A couple of weeks ago I posted a post about creating a couple of data generators for DataDude. In mine first data generator I used aggregation extensibility. Microsoft divided each form of extensibility in another section. Since I created one or more instances of the standard data generator classes and use those to do the work its called aggregation extensibility. Another thing I created is the use of multiple generators in one class. The base class implementation constructs outputs that are based on public properties that are marked with the OutputAttribute. The inputs are set by using the InputAttribute. Using properties that are marked with attributes provides a simple mechanism for declaring input and output values that are strongly typed. The code below uses multiple outputs, a numeric and a regular expression data generator to do the work.

    1 using Microsoft.VisualStudio.TeamSystem.Data.DataGenerator;
    2 using Microsoft.VisualStudio.TeamSystem.Data.Generators;
    3 using System;
    4 using System.Globalization;
    5 
    6 namespace DutchGenerator
    7 {
    8     public class DutchDataDetails : Generator
    9     {
   10         #region Fields
   11         private string _prefix;
   12         private string _seperator;
   13         private CasingType _casing;
   14 
   15         private RegexString _regex;
   16         private Int _numeric;
   17         private Random _rand = new Random();
   18 
   19         private string genPostCode;
   20         private string genProvince;
   21         private string genProvinceAbbrev;
   22         private string genPhoneNumber;
   23         private string genMobilePhoneNumber;
   24         #endregion
   25 
   26         #region Enumerators
   27         public enum CasingType
   28         {
   29             NormalCase,
   30             LowerCase,
   31             UpperCase,
   32             TitleCase
   33         }
   34 
   35         private string[] Provinces = new string[] {
   36               "Drenthe"
   37             , "Flevoland"
   38             , "Friesland"
   39             , "Gelderland"
   40             , "Groningen"
   41             , "Limburg"
   42             , "Noord-Brabant"
   43             , "Noord-Holland"
   44             , "Overijssel"
   45             , "Utrecht"
   46             , "Zeeland"
   47             , "Zuid-Holland"};
   48 
   49         private string[] ProvincesAbbrev = new string[]
   50         {
   51               "DR"
   52             , "FL" 
   53             , "FR"
   54             , "GL"
   55             , "GR"
   56             , "LI"
   57             , "NB"
   58             , "NH"
   59             , "OV"
   60             , "UT"
   61             , "ZH"
   62             , "ZL"
   63         };
   64         #endregion
   65 
   66         #region Inputs
   67         [Input(Name = "Prefix", Description = "Prefix for (mobile)phonenumbers. (e.g. +31)", DefaultValue = "")]
   68         public string Prefix
   69         {
   70             get
   71             {
   72                 return _prefix;
   73             }
   74             set
   75             {
   76                 _prefix = value;
   77             }
   78         }
   79 
   80         [Input(Name = "Seperator", Description = "Seperator for postcodes (e.g. space char) and (mobile)phonenumbers (e.g. parenthes and minus char).", DefaultValue = " ")]
   81         public string Seperator
   82         {
   83             get
   84             {
   85                 return _seperator;
   86             }
   87             set
   88             {
   89                 _seperator = value;
   90             }
   91         }
   92 
   93         [Input(Name = "Character casing", Description = "Character casing of generated string.", DefaultValue = CasingType.NormalCase)]
   94         public CasingType CharacterCasing
   95         {
   96             get
   97             {
   98                 return _casing;
   99             }
  100             set
  101             {
  102                 _casing = value;
  103             }
  104         }
  105         #endregion
  106 
  107         #region Outputs
  108         [Output(Name = "Province name", Description = "The long form of the province name.")]
  109         public string Province
  110         {
  111             get
  112             {
  113                 return genProvince;
  114             }
  115         }
  116 
  117         [Output(Name = "Province abbreviation", Description = "The 2 letter form of the province name.")]
  118         public string ProvinceAbbrev
  119         {
  120             get
  121             {
  122                 return genProvinceAbbrev;
  123             }
  124         }
  125 
  126         [Output(Name = "Postcode", Description = "A valid _regex.")]
  127         public string PostCode
  128         {
  129             get
  130             {
  131                 return genPostCode;
  132             }
  133         }
  134 
  135         [Output(Name = "Phonenumber", Description = "A valid phonenumber, including area code.")]
  136         public string PhoneNumber
  137         {
  138             get
  139             {
  140                 return genPhoneNumber;
  141             }
  142         }
  143 
  144         [Output(Name = "Mobile Phonenumber", Description = "A valid mobile phonenumber.")]
  145         public string MobilePhoneNumber
  146         {
  147             get
  148             {
  149                 return genMobilePhoneNumber;
  150             }
  151         }
  152         #endregion
  153 
  154         protected string FormatOutputValue(string originalOutput)
  155         {
  156             string formattedValue = String.Empty;
  157 
  158             if (originalOutput != null)
  159             {
  160                 //format character casing
  161                 switch (_casing)
  162                 {
  163                     case CasingType.NormalCase:
  164                         formattedValue = originalOutput;
  165                         break;
  166 
  167                     case CasingType.LowerCase:
  168                         formattedValue = CultureInfo.CurrentCulture.TextInfo.ToLower(originalOutput);
  169                         break;
  170 
  171                     case CasingType.UpperCase:
  172                         formattedValue = CultureInfo.CurrentCulture.TextInfo.ToUpper(originalOutput);
  173                         break;
  174 
  175                     case CasingType.TitleCase:
  176                         formattedValue = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(originalOutput);
  177                         break;
  178 
  179                     default:
  180                         break;
  181                 }
  182             }
  183 
  184             return formattedValue;
  185         }
  186 
  187         protected override void OnInitialize(GeneratorInit initInfo)
  188         {
  189             base.OnInitialize(initInfo);
  190 
  191             switch (this.OutputKey)
  192             {
  193                 case "Province":
  194 
  195                 case "ProvinceAbbrev":
  196                     _numeric = new Int();
  197                     _numeric.Distribution = new Normal();
  198                     _numeric.Min = 0;
  199                     _numeric.Max = 11;
  200                     _numeric.Initialize(initInfo);
  201                     break;
  202 
  203                 case "PostCode":
  204                     _regex = new RegexString();
  205                     _regex.Expression = "[1-9]{1}[0-9]{3}##([a-z]|[A-Z]){2}";
  206                     _regex.MaximumLength = 8;
  207                     _regex.Initialize(initInfo);
  208                     break;
  209 
  210                 case "PhoneNumber":
  211                     _regex = new RegexString();
  212                     _regex.Expression = "0[123457]{1}[0-9]{1}##[0-9]{7}";
  213                     _regex.MaximumLength = 12;
  214                     _regex.Initialize(initInfo);
  215                     break;
  216 
  217                 case "MobilePhoneNumber":
  218                     _regex = new RegexString();
  219                     _regex.Expression = "06##[0-9]{8}";
  220                     _regex.MaximumLength = 12;
  221                     _regex.Initialize(initInfo);
  222                     break;
  223 
  224                 default:
  225                     break;
  226             }
  227         }
  228 
  229         protected override void OnGenerateNextValues()
  230         {
  231             string result;
  232             base.OnGenerateNextValues();
  233 
  234             switch (this.OutputKey)
  235             {
  236                 case "Province":
  237                     _numeric.GenerateNextValues();
  238                     genProvince = FormatOutputValue(Provinces[_numeric.Result]);
  239                     break;
  240 
  241                 case "ProvinceAbbrev":
  242                     _numeric.GenerateNextValues();
  243                     genProvinceAbbrev = FormatOutputValue(ProvincesAbbrev[_numeric.Result]);
  244                     break;
  245 
  246                 case "PostCode":
  247                     _regex.GenerateNextValues();
  248                     genPostCode = FormatOutputValue(_regex.Result.Replace("##", _seperator));
  249                     break;
  250 
  251                 case "PhoneNumber":
  252                     _regex.GenerateNextValues();
  253                     result = _regex.Result.Replace("##", _seperator);
  254 
  255                     if ((_seperator.Length > 0) && (_rand.Next(0, 2) == 0))
  256                     {
  257                         result = result.Replace(_seperator, result.Substring(3 + _seperator.Length, 1));
  258                         result = result.Remove(4, 1);
  259                         result = result.Insert(4, _seperator);
  260                     }
  261                     genPhoneNumber = FormatOutputValue(_prefix.Length > 0 ? _prefix + result.Substring(1) : result);
  262                     break;
  263 
  264                 case "MobilePhoneNumber":
  265                     _regex.GenerateNextValues();
  266                     result = _regex.Result.Replace("##", _seperator);
  267 
  268                     genMobilePhoneNumber = FormatOutputValue(_prefix.Length > 0 ? _prefix + result.Substring(1) : result);
  269                     break;
  270 
  271                 default:
  272                     break;
  273             }
  274 
  275         }
  276 
  277     }
  278 }
  279 

Dutch data generator (Provinces, Postcodes, Phone – and Mobile phone numbers)

To register the class in the Visual Studio environment a XML file has to be created where all objects are registered through

  • XML file naming convention <AssemblyFilename>.Extensions.xml
  • The file extension .Extensions.xml is mandatory
  • The Extensions.xml file has to be placed in the %ProgramFiles%Microsoft Visual Studio 8DBPro directory
  • Must implement the schema defined in %ProgramFiles%Microsoft Visual Studio 8DBPro Microsoft.VisualStudio.TeamSystem.Data.Extensions.xsd
<?xml version="1.0" encoding="us-ascii"?>
<extensions assembly="DutchGenerator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d6ddc2e862439c84" version="1" xmlns="urn:Microsoft.VisualStudio.TeamSystem.Data.Extensions" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:Microsoft.VisualStudio.TeamSystem.Data.Extensions
  Microsoft.VisualStudio.TeamSystem.Data.Extensions.xsd">
    <extension type="DutchGenerator.DutchDataDetails" enabled="true" />
</extensions>

DutchGenerator.Extensions.xml

There are two options where the extension assemblies can be placed

  • %ProgramFiles%Microsoft Visual Studio 8DBProExtensions directory
  • GAC, requires the assembly to be strong name signed

Do use the extension assembly the following steps have to be done

  • Copy .extensions.xml file to DBPro directory
  • Copy assembly to DBProExtensions directory
  • GAC the extension assembly

Since this isn’t handy when your developing you Extension, you could create a generic post build event:

copy "$(ProjectDir)$(TargetName).Extensions.xml" "$(ProgramFiles)Microsoft Visual Studio 8DBPro$(TargetName).Extensions.xml" /y 
 
copy "$(TargetDir)$(TargetFileName)" "$(ProgramFiles)Microsoft Visual Studio 8DBProExtensions$(TargetFileName)" /y 
 
"$(DevEnvDir)....SDKv2.0bingacutil.exe" /if "$(ProgramFiles)Microsoft Visual Studio 8DBProExtensions$(TargetFileName)"
 

In the next screendumps the Dutch Data Generator is used to select and modify one of the data generator outputs and create the generated output.

properties
Generator properties

datadetails 
Generated output

Summary
I love the data generators option. More and more often companies prohibited you to use you production data in a test environment, probably because of SOX (Sarbanes-Oxley). So you have to create your own test data with the same amount of records. Before DataDude this was hard to do, but with DataDude you can do this easily and even create your own generators. I hope lots of people will create useful generators and maybe we can collect them on codeplex.

 

One thought on “Created my first custom data generator for VSTE DBPro (aka DataDude)”

  1. >>I hope lots of people will create useful generators and maybe we can collect them on codeplex.

    Just found your blog post. You have much better explanation on how to use generators ;).

    Anyway, few weeks ago I have posted new project here http://www.codeplex.com/DbProGenerators
    At the present time it has just only one generator, namely LoremTextGenerator (it just render series of the LoremIpsum-like strings). But I hope I will be able to do more :).

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>