DataMasker 3.0.2

dotnet add package DataMasker --version 3.0.2
                    
NuGet\Install-Package DataMasker -Version 3.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DataMasker" Version="3.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DataMasker" Version="3.0.2" />
                    
Directory.Packages.props
<PackageReference Include="DataMasker" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DataMasker --version 3.0.2
                    
#r "nuget: DataMasker, 3.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DataMasker@3.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DataMasker&version=3.0.2
                    
Install as a Cake Addin
#tool nuget:?package=DataMasker&version=3.0.2
                    
Install as a Cake Tool

.NET Core

DataMasker

A free data masking/anonymizer library for Sql Server written in .NET

If you've ever needed to pull down databases from a live environment to stage or even dev you'll need to think about masking any personal information. There are options out there paid and free, however the free ones I've found do not provide genuine data and the paid options are too pricey when it's only a few tables.

Data generation is provided by https://github.com/bchavez/Bogus

Bogus

Hello. I'm your host Brian Chavez (twitter). Bogus is a simple and sane fake data generator for .NET languages like C#, F# and VB.NET. Bogus is fundamentally a C# port of faker.js and inspired by FluentValidation's syntax sugar.

Bogus will help you load databases, UI and apps with fake data for your testing needs. If you like Bogus star ⭐️ the repository and show your friends! 😄

Upgrading to 3.0

Version 3.0 is a breaking release. Before upgrading, note the following:

  • .NET 10 is required. The library and the CLI now target net10.0. .NET 8 is no longer supported.
  • Microsoft.Data.SqlClient replaces System.Data.SqlClient. If you construct a SqlDataProvider yourself, pass a Microsoft.Data.SqlClient.SqlConnection. Microsoft.Data.SqlClient encrypts connections by default (Encrypt=True), so a server without a trusted certificate will now fail to connect unless your connection string includes TrustServerCertificate=True or Encrypt=False. The old client did not enforce this.
  • Releases are automatic. Every push to master publishes a new 3.0.x version to NuGet and GitHub Releases.
  • IDataSource and IDataMasker have grown. IDataSource gains GetDistinctValues(tableConfig, columnName) and IDataMasker gains SeedUniqueValues(...). If you implement either interface yourself you will need to add these. If you use the built-in DataMasker directly, call dataMasker.SeedUniqueValues(dataSource, tableConfig) before masking a table so that unique columns never collide with values already in the table.
  • Rows are read in pages and updated in bulk. SqlDataSource.GetData streams the table in primary key order, one batch at a time, instead of loading it all into memory. Updates against SQL Server are bulk copied into a temp table and applied with a single UPDATE ... JOIN per batch. The primary key must be a single sortable column (it already had to be for updates).
  • updateBatchSize defaults to 1000 instead of the whole table, so a large table is no longer one enormous transaction. A new commandTimeout setting (seconds, default 600, 0 for no limit) applies to every query, including sqlStatements.
  • The CLI returns a non-zero exit code on failure (1 when masking fails, 2 for bad arguments) and always writes the error to stderr, even with --no-output.

Nuget

DataMasker is available as a library over on nuget.org should you need to customize any part of the process. Alternatively just ask for a new feature and I'll see what I can do.

install-package DataMasker

Configuration

All configuration is done via a .json file, of which an example can be seen below.

Example Config

{
  "dataSource": {
    "type": "SqlServer",
    "config": {
       "name": "databasename",
       "userName": "databaseUserName",
       "password": "databasePassword",
       "server": "databaseServer"
    }   
  },  
  "tables": [    
    {
      "name": "Users",
      "schema": "dbo",
      "primaryKeyColumn": "UserId",
      "columns": [
        {
          "name": "FirstName",
          "type": "FirstName",
          "useGenderColumn": "Gender"
        },
        {
          "name": "LastName",
          "type": "LastName",
          "retainEmptyStringValues": true,
          "useGlobalValueMappings": true,
          "valueMappings": {
            "oldValue": "Jones",
            "newValue": "Smith"
          }
        },
        {
          "name": "Password",
          "type": "None",
          "retainNullValues": false,
          "useValue": "PasswordForDevEnvironment"
        },
        {
          "name": "DOB",
          "type": "DateOfBirth",
          "min" :"1901-01-01",
          "max": "2000-0101"
        },
        {
          "name": "EmailAddress",
          "type": "Bogus",
          "unique": true,
          "stringFormatPattern": "{{internet.email}}"
        },
        {
          "name": "Gender",
          "ignore": true,
          "type": "None"
        },
        {
          "name": "Address",
          "type": "Bogus",
          "retainNullValues": false,
          "stringFormatPattern": "{{address.fullAddress}}"
        },
        {
          "name": "ContactNumber",
          "type": "PhoneNumber",
          "retainNullValues": false,
          "stringFormatPattern": "+447#########"
        },
        {
          "name": "CompanyName",
          "type": "Sql",
          "sqlValue": {
            "query":"SELECT Name FROM SampleCompanyNames WHERE UserId = @UserId",
            "valueHandling": "Null"
          }
        }
      ]
    }
  ]
}

Table configuration is supplied by either the "tables" property or "tablesConfigPath" property.

If you want to keep your table configuration external to the main .json file this can be done by supplying "tablesConfigPath" with a file path. An example of this can be found in the example-configs directory in the example project.

External table config json

{
    "dataSource": {
    "type": "SqlServer",
    "config": {
       "name": "databasename",
       "userName": "databaseUserName",
       "password": "databasePassword",
       "server": "databaseServer"
    }   
  }, 
  "tablesConfigPath": "example-configs\\config-example2-table-config.json",
}

or embedded

{
    "dataSource": {
    "type": "SqlServer",
    "config": {
       "name": "databasename",
       "userName": "databaseUserName",
       "password": "databasePassword",
       "server": "databaseServer"
    }   
  }, 
  "tables": [... insert config here ...],
}

If both tables and tablesConfigPath is supplied then tablesConfigPath wins.

Column Configuration

Property Name Values
type None, Bogus, FirstName, lastName, DateOfBirth, Rant, StringFormat, FullAddress, PhoneNumber
name Database column name
schema Name of the schema in which the tables lives, defaults to dbo
valueMappings Object with value mappings, e.g map "James" to "John"
useGenderColumn Name of the database column to use as for the gender
ignore true/false
min Minimum value to use for the given data type
max Maxiumum value to use for the given data type
stringFormatPattern From Bogus, numbers #, letters ?, or * random number or letter
useValue A hardcoded value to use for every row
useList An array of values to randomly pick from for each row, e.g. [10, 20, 56, 80]
retainNullValues true/false
retainEmptyStringValues true/false - when true if the existing value is null or empty (whitespace) then it will use the original value
useLocalValueMappings true/false
useGlobalValueMappings true/false
unique true/false - when true it will attempt to generate a unique value for this column. Values already in the table are excluded as well (the CLI does this automatically, library users call SeedUniqueValues). Can be combined with value mappings: the same source value always gets the same unique replacement.
sqlValue Used when type is set to sql

Sql Value Configuration

When using data type Sql this allows you to get values from other tables within the same database. The configuration object is made up of the following properties

Property Name Values
query The query to use for the lookup, the current row will be passed into the query as parameters for use, see the example config above that uses @UserId. Columns are passed back into the query as parameters, any columns with spaces in their name, will be replaced with '_' . e.g. A column name "User Id" would become "User_Id"
valueHandling "Null" or "KeepValue". If the query executes and no data is returned, this tells the masker what to do, null will set the value to Null while KeepValue will keep the existing value on that row

Data types

None

To use None you must specify either valueMappings, useValue, or useList, no data will be generated for this type. If you specify only valueMappings and the target value is not found, an error will be thrown.

{
  "name": "Title",
  "type": "None",
  "valueMappings": {
    "Mr": "Master"
  },
  "ignore":"true/false",
  "useValue": "Miss",
  "retainNullValues": "true/false",
  "retainEmptyStringValues": "true/false",
  "useLocalValueMappings": "true/false",
  "useGlobalValueMappings": "true/false"
}
useList

Randomly selects a value from a predefined list for each row. Works with any column type and supports integers, strings, or any JSON-compatible value.

{
  "name": "ProductTypeId",
  "type": "None",
  "useList": [10, 20, 56, 80],
  "retainNullValues": false
}

With strings:

{
  "name": "Status",
  "type": "None",
  "useList": ["Active", "Inactive", "Pending"],
  "retainNullValues": false
}
Bogus

Bogus is a type that when specified requires the stringFormatPattern option, which is passed directly to the Bogus API, see here for available options

{
  "name": "PhoneNumber",
  "type": "Bogus",
  "valueMappings": {    
    "+555-555-555": "+444-555-555-55"
  },
  "ignore":"true/false",
  "useValue": "+50559-5-5-555",
  "retainNullValues": "true/false",
  "retainEmptyStringValues": "true/false",
  "useLocalValueMappings": "true/false",
  "useGlobalValueMappings": "true/false",
  "stringFormatPattern": "{{phonenumbers.phonenumber}}"
}
FirstName
{
  "name": "FirstName",
  "type": "FirstName",
  "valueMappings": {    
    "James": "Bob"
  },
  "ignore":"true/false",
  "useValue": "Steve",
  "retainNullValues": "true/false",
  "retainEmptyStringValues": "true/false",
  "useLocalValueMappings": "true/false",
  "useGlobalValueMappings": "true/false",
  "useGenderColumn": "Gender"
}
LastName
{
  "name": "Surname",
  "type": "LastName",
  "valueMappings": {    
    "Smith": "Jojnes"
  },
  "ignore":"true/false",
  "useValue": "Timms",
  "retainNullValues": "true/false",
  "retainEmptyStringValues": "true/false",
  "useLocalValueMappings": "true/false",
  "useGlobalValueMappings": "true/false"
}
DateOfBirth
  • min will default to 1901-01-01
  • max will default to current date
  • date format is fullyear-month-day
{
  "name": "DOB",
  "type": "DateOfBirth",
  "valueMappings": {    
    "1990-01-02": "1990-02-02"
  },
  "ignore":"true/false",
  "useValue": "1940-02-02",
  "retainNullValues": "true/false",
  "retainEmptyStringValues": "true/false",
  "useLocalValueMappings": "true/false",
  "useGlobalValueMappings": "true/false",
  "min": "1901-12-25",
  "max": "2000-11-20"
}
Rant
  • max will default to 25
{
  "name": "Comments",
  "type": "Rant",
  "valueMappings": {    
    "A comment": "Becomes this"
  },
  "ignore":"true/false",
  "useValue": "A really important comment",
  "retainNullValues": "true/false",
  "retainEmptyStringValues": "true/false",
  "useLocalValueMappings": "true/false",
  "useGlobalValueMappings": "true/false",
  "max": 15
}
Lorem
  • min will default to 5
  • max will default to 30
{
  "name": "Comments",
  "type": "Lorem",
  "valueMappings": {    
    "A comment": "Becomes this"
  },
  "ignore":"true/false",
  "useValue": "A really important comment",
  "retainNullValues": "true/false",
  "retainEmptyStringValues": "true/false",
  "useLocalValueMappings": "true/false",
  "useGlobalValueMappings": "true/false",
  "min": 5,
  "max": 15
}
StringFormat

Check out the Bogus API for supported values

{
  "name": "Comments",
  "type": "StringFormat",
  "valueMappings": {    
    "A comment": "Becomes this"
  },
  "ignore":"true/false",
  "useValue": "A really important comment",
  "retainNullValues": "true/false",
  "retainEmptyStringValues": "true/false",
  "useLocalValueMappings": "true/false",
  "useGlobalValueMappings": "true/false",
  "stringFormatPattern": "#####****?????"
}
FullAddress
{
  "name": "Address",
  "type": "FullAddress",
  "valueMappings": {    
    "55 Long Name Street, Long Name Village, Long Name Town...": "Becomes this"
  },
  "ignore":"true/false",
  "useValue": "55 Long Name Street, Long Name Village, Long Name Town...",
  "retainNullValues": "true/false",
  "retainEmptyStringValues": "true/false",
  "useLocalValueMappings": "true/false",
  "useGlobalValueMappings": "true/false",
}
PhoneNumber
{
  "name": "PhoneNumber",
  "type": "PhoneNumber",
  "valueMappings": {    
    "+555-555-555": "+444-555-555-55"
  },
  "ignore":"true/false",
  "useValue": "+50559-5-5-555",
  "retainNullValues": "true/false",
  "retainEmptyStringValues": "true/false",
  "useLocalValueMappings": "true/false",
  "useGlobalValueMappings": "true/false",
  "stringFormatPattern": "+1 ########-#-###-#"
}
Sql

The current row is passed in as parameters and can be accessed using @ColumnName

{
  "name":"CompanyName",
  "type": "Sql",
  "sqlValue": {
    "query":"SELECT Name FROM SampleCompanyNames WHERE UserId = @UserId",
    "valueHandling": "Null/KeepValue"
  }
}

name & type are required everything else is optional unless specified

Most data can be generated perfectly fine just by using the Bogus or StringFormat data types.

Min & max

Only some data types currently use the min/max properties on the column configurations.

  • Lorem, Rant & DOB

Example Usage

//load our configuration
Config config = Config.Load($"example-configs\\config-example1.json")
// you also can pass the JSON content directly:
// Config config = Config.LoadFromString(....);

//create a data masker
IDataMasker dataMasker = new DataMasker(new DataGenerator(config.DataGeneration));

//grab our dataSource from the config, note: you could just ignore the config.DataSource.Type
//and initialize your own instance
IDataSource dataSource = DataSourceProvider.Provide(config.DataSource.Type, config.DataSource);

//Enumerable all our tables and begin masking the data
foreach (TableConfig tableConfig in config.Tables)
{
    //load the data, this needs optimizing for large tables
    IEnumerable<IDictionary<string, object>> rows = dataSource.GetData(tableConfig);
    foreach (IDictionary<string, object> row in rows)
    {
        //mask each row
        dataMasker.Mask(row, tableConfig);

        //update per row, or see below,
        //dataSource.UpdateRow(row, tableConfig);
    }

    //update all rows, in batches of 100
    dataSource.UpdateRows(rows, tableConfig, 100);
}

All of the objects/datatypes from Bogus are supported, you can use the type "Bogus" in combination with "stringFormatPattern" to acheive any valueMappings

{
  "name": "Address",
  "type": "Bogus",
  "stringFormatPattern": "{{address.fullAddress}}"
}

You can combine multiple objects to generate complex data

{
  "name": "Name",
  "type": "Bogus",
  "stringFormatPattern": "{{name.prefix}} {{name.firstName}} {{name.lastName}}"
}

Data Sources

There are only two DataSources available at the moment

  • InMemoryFake - is there only for the examples
  • SqlServer - can pull and push data to SQL Server

There is some additional configuration required when using SqlServer, on the dataSource object a dynamic config property is available, you'll need to supply the name, server, userName & password for the connection or a connection string.

N.B. if the "connectionString" value is set name, server, userName & password will be ignored

{
  "dataSource":{
    "config":{
      "name": "xxx",
      "server": "xxx",
      "userName": "xxx",
      "password": "xxx",
      "connectionString": "Data Source=(localdb)\\mssqllocaldb;Initial Catalog=Clients;Integrated Security=SSPI;"
    }
  }
}

The dataSource object also accepts the following optional settings:

Setting Default Description
dryRun false Run everything inside transactions that are rolled back. sqlStatements are skipped entirely.
updateBatchSize 1000 Number of rows read and updated per batch. Each batch is its own transaction.
commandTimeout 600 Timeout in seconds for every query the data source runs, including sqlStatements. 0 means no timeout.
{
  "dataSource":{
    "type": "SqlServer",
    "dryRun": false,
    "updateBatchSize": 5000,
    "commandTimeout": 1800,
    "config":{ "connectionString": "..." }
  }
}

SqlServer Data Source

Rows are read in pages ordered by the primary key, so memory use is bounded by updateBatchSize rather than the size of the table. Each batch is bulk copied into a temp table and applied with one UPDATE ... JOIN, which is far quicker than a statement per row on large tables.

Dry run is supported. A transaction is created, the batch is applied and then the transaction is rolled back.

Gender

To ensure the new data is more accurate you may want to take gender into consideration when generating certain data types such as names. This can be achieved with a small amount of additional configuration. If no gender is specified then non gender specific names are generated.

You must define the gender column and then tell your target column to use this when generating data.

Here we are saying, use the column "Gender" when generating data for "FirstName". We then include the "Gender" column but tell it to be ignored by the IDataMasker, it is purley there as a dependency to "FirstName".

"columns": [
  {
    "name": "FirstName",
    "type": "FirstName",
    "useGenderColumn": "Gender"
  },
  {
    "name": "Gender",
    "ignore": true,
    "type": "None"
  },  
]

Locale

By default the locale is "en", the locale.

The locale is used by Bogus to generate data, the locale can be changedby setting a property on the dataGeneration object

{
  "dataGeneration": {
      "locale": "en"
    }  
}

Check out the Bogus page for a list of supported locales

DataMasker.Runner (CLI)

The latest version can be found at https://github.com/Steveiwonder/DataMasker/releases

The CLI is published as a self-contained Windows x64 build, so no separate .NET install is needed to run it.

This is a CLI interface for the data masking tool. You might want to use this as part of your continuous integration if you backup/restore your live environments back to stage/dev after a release

The exit code is 0 on success, 1 if masking fails and 2 if the arguments could not be parsed. Errors are written to stderr even when --no-output is set, so a CI pipeline can rely on the exit code.

The options are as follows

-c, --config-file Required. the json configuration to be

-d, --dry-run (Default: false) if set, performs a dry run (changes are rolled back), only supported by some data sources

-l, --locale set the locale

-u, --update-batchsize batch size to use when upating records

--print-options (Default: false) prints the arguments passed into this tool in a json format with executing anything else

--no-output (Default: false) if set, no output to the console will be written

--help Display this help screen.

--version Display version information.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on DataMasker:

Package Downloads
DataMasker.Main

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.2 8 11/6/2026
2.0.62 686 4/20/2026
2.0.61 477 4/23/2024
2.0.60 335 3/28/2024
2.0.59 338 2/17/2024
2.0.58 355 2/12/2024
2.0.57 366 10/11/2023
2.0.56 329 9/26/2023
2.0.55 317 9/25/2023
2.0.54 323 9/25/2023
2.0.53 323 9/25/2023
2.0.52 322 9/25/2023
2.0.51 315 9/25/2023
2.0.48 310 9/25/2023
2.0.47 381 8/10/2023
2.0.46 374 8/10/2023
2.0.45 353 8/10/2023
2.0.44 376 8/10/2023
2.0.43 376 8/10/2023
2.0.40 1,067 8/9/2021
Loading failed