Skip to content

Generating multiple database contexts in a single go

Simon Hughes edited this page Oct 12, 2022 · 20 revisions

This is an Enterprise feature which is new v3.

This will read in the filter and setting requirements from a database table and will have the ability to generate many DB contexts, poco, etc all in a single go. The database is read just once, and many (can be 100's) database contexts are generated, with just the specific tables, columns and stored procs you want in each DB context, each within its own namespace.

This is ideal if you have high-security requirements as you don’t want any website/API/microservice with the capability to query extra tables or columns than is strictly necessary.

Firstly, you need to create the multi-context settings tables. Run the following SQL script on SQL Server: MultiContextSettings.sql

In that script, there are examples of how to insert data into the multi-context settings tables.

Only the exact tables, columns, stored procedures, functions and enumerations as specified in the multi-context settings tables are generated. All others are excluded.

In your <database>.tt file, include the following, and put in a connection string to the database containing the above multi-context settings tables:

Settings.GenerateSingleDbContext = false;
Settings.MultiContextSettingsConnectionString = "";
// "Data Source=(local);Initial Catalog=EfrpgTest_Settings;Integrated Security=True;Encrypt=false;TrustServerCertificate=true";

Here is an example of a complete <database>.tt you need for multi-context generation

<#@ include file="EF.Reverse.POCO.v3.ttinclude" #>
<#
    // Multi context single files
    Settings.DatabaseType            = DatabaseType.SqlServer; // SqlServer, SqlCe. Coming next: PostgreSQL, MySql, Oracle
    Settings.TemplateType            = TemplateType.Ef6; // Ef6, EfCore, FileBased. FileBased specify folder using Settings.TemplateFolder
    Settings.GeneratorType           = GeneratorType.Ef6; // Ef6, EfCore, Custom. Custom edit GeneratorCustom class to provide your own implementation
    Settings.UseMappingTables        = true; // Set to false for EfCore. EFCore will add support for this in v3. If true, mapping will be used and no mapping tables will be generated. If false, all tables will be generated.
    Settings.FileManagerType         = FileManagerType.VisualStudio; // VisualStudio = .NET project; Custom = .NET Core project; Null = No output (testing only)
    Settings.ConnectionString        = "Data Source=(local);Initial Catalog=EfrpgTest;Integrated Security=True;Encrypt=false;TrustServerCertificate=true";
    Settings.ConnectionStringName    = "MyDbContext"; // ConnectionString key as specified in your app.config/web.config/appsettings.json
    Settings.GenerateSeparateFiles   = false;
    Settings.Namespace               = DefaultNamespace; // Override the default namespace here
    Settings.AddUnitTestingDbContext = false;

    Settings.GenerateSingleDbContext = false;
    Settings.MultiContextSettingsConnectionString = "Data Source=(local);Initial Catalog=EfrpgTest_Settings;Integrated Security=True;Encrypt=false;TrustServerCertificate=true";

    Inflector.PluralisationService = new EnglishPluralizationService();
    var outer = (GeneratedTextTransformation) this;
    var fileManagement = new FileManagementService(outer);
    var generator = GeneratorFactory.Create(fileManagement, FileManagerFactory.GetFileManagerType());
    if (generator.InitialisationOk)
    {
        generator.ReadDatabase();
        generator.GenerateCode();
    }
    fileManagement.Process();#>

If you are generating multiple contexts which contain the same table name(s), I would recommend setting Settings.GenerateSeparateFiles = false; as this will prevent overwriting of poco classes by the different contexts.

Custom attributes on columns

Use a Tilda ~ delimited list of attributes to add to a poco property. You can change the delimiter by changing MultiContextColumnAttributeDelimiter = '~'; Example:

UPDATE MultiContext.[Column]
SET    Attributes='[ExampleForTesting("abc")]~[CustomRequired]'
WHERE  Name = 'Dollar';

UPDATE MultiContext.[Column] 
SET    Attributes='[ExampleForTesting("def")]~[CustomSecurity(Readonly)]'
WHERE  Name = 'Pound';

This will add the attributes as follows:

[ExampleForTesting("abc")]
[CustomRequired]
public int Dollar { get; set; } // $ (Primary key)

[ExampleForTesting("def")]
[CustomSecurity(SecurityEnum.Readonly)]
public int? Pound { get; set; } // £

Custom file-based templates

This is activated if Settings.TemplateType = TemplateType.FileBased; You can provide a default template folder for your context by setting:

Settings.TemplateFolder = "C:\\path_to_templates";

If the path is relative to your project, you can use:

Settings.TemplateFolder = Path.Combine(Settings.Root, "Templates");

The Settings.TemplateFolder path will be used for all templates where it is not overridden by the MultiContext.Context.TemplatePath field for that particular context.

To set a custom template path for a context:

UPDATE MultiContext.Context
SET    TemplatePath = 'C:\\path_to_different_templates'
WHERE  [Name] = 'name_of_your_context'

For example, having:

Settings.TemplateFolder = "c:\\test";

And 3 DB contexts, with two having TemplatePath = NULL, and one having:

UPDATE MultiContext.Context
SET    TemplatePath = 'C:\\java_templates'
WHERE  [Name] = 'Java'

Contexts one and two will use templates from c:\test, and template three "Java" will use templates from C:\java_template\. Very useful if you want to generate custom classes, and have set Settings.ElementsToGenerate = Elements.Poco;

Extra columns for custom processing

The multi-context setting tables found in MultiContextSettings.sql can include extra columns for custom processing. All columns are read in and stored in a dictionary for you to use. To process this data, edit the bottom of MultiContextFilter.UpdateColumn() function, here I have included some example code you can uncomment out and use. In the following example, I am simply updating the column ExtendedProperty so it gets output in a summary block, however, you could do all sorts of things to the column, such as mark it as hidden, etc:

By running the following SQL in your settings DB:

ALTER TABLE MultiContext.[Column] ADD Test VARCHAR(10) NULL;
ALTER TABLE MultiContext.[Column] ADD DummyInt int NULL;
ALTER TABLE MultiContext.[Column] ADD date_of_birth DATETIME NULL;

It will give you access to extra custom fields. Those fields can be used within the MultiContextFilter.UpdateColumn() function. Example:

if (col.AllFields != null)
{
    // INT example
    if (col.AllFields.ContainsKey("DummyInt"))
    {
        var o = col.AllFields["DummyInt"];
        var dummyInt = (int) o;
        column.ExtendedProperty += string.Format(" DummyInt = {0}", dummyInt);
    }

    // VARCHAR example
    if (col.AllFields.ContainsKey("Test"))
    {
        var o = col.AllFields["Test"];
        var test = o.ToString();
        column.ExtendedProperty += string.Format(" Test = {0}", test);
    }

    // DATETIME example
    if (col.AllFields.ContainsKey("date_of_birth"))
    {
        var o = col.AllFields["date_of_birth"];
        var date = Convert.ToDateTime(o);
        column.ExtendedProperty += string.Format(" date_of_birth = {0}", date.ToLongDateString());
    }
}