Application configuration in code using RuntimeConfiguration

The generated code, both adapter and selfservicing, offer several application-wide configuration settings which can be set through the usage of the RuntimeConfiguration class. For .NET full, you can, besides using the RuntimeConfiguration class, also use Application configuration through a .config file. For .NET Standard you're required to use the RuntimeConfiguration class for configuring the LLBLGen Pro Runtime Framework as on .NET Standard/core there are no .config files.

RuntimeConfiguration and application startup

It's essential that your code calling RuntimeConfiguration methods and properties is run as soon as possible after your application starts. This way you avoid using the LLBLGen Pro runtime framework internals without configuring them properly. E.g. if you use ASP.NET Core, a good place to call RuntimeConfiguration methods is in the Configure method of ASP.NET Core Startup class.

Example of RuntimeConfiguration usage

Below is an example of how using the RuntimeConfiguration class to configure the LLBLGen Pro Runtime Framework at runtime looks like. The example below doesn't contain every option available.

// ... this code is placed in a method called at application startup
RuntimeConfiguration.AddConnectionString("ConnectionString.SQL Server (SqlClient)", 
                                    "data source=nerd;initial catalog=Northwind;integrated security=SSPI;persist security info=False;packet size=4096");
// Configure the DQE
RuntimeConfiguration.ConfigureDQE<SQLServerDQEConfiguration>(
                                c => c.SetTraceLevel(TraceLevel.Verbose)
                                        .AddDbProviderFactory(typeof(System.Data.SqlClient.SqlClientFactory))
                                        .SetDefaultCompatibilityLevel(SqlServerCompatibilityLevel.SqlServer2012));
// Configure Dependency Injection
RuntimeConfiguration.SetDependencyInjectionInfo(
                                    new List<Assembly>()
                                    {
                                        typeof(AddressEntity).Assembly,
                                        this.GetType().Assembly
                                    }, 
                                    new List<string>()
                                    {
                                        "Northwind", 
                                        "Authorizers",
                                    });
// Configure tracers
RuntimeConfiguration.Tracing
                        .SetTraceLevel("ORMPersistenceExecution", TraceLevel.Info);
                        .SetTraceLevel("ORMPlainSQLQueryExecution", TraceLevel.Info);
// Configure entity related settings
RuntimeConfiguration.Entity
                        .SetMarkSavedEntitiesAsFetched(true)
                        .SetMakeInvalidFieldReadsFatal(true);

The following sections will describe the methods used above as well as other, not illustated, options usable to configure the runtime framework.

Connection string configuration

To specify a connection string for your application, use the following method:

RuntimeConfiguration.AddConnectionString("Key", "ConnectionString");

Key is the name of the connection string as it's generated in the generated app.config file in the generated code. This file is generated for adapter in the DBSpecific project folder, and for SelfServicing it's generated in the generated code output folder. ConnectionString is the connection string as generated in the app.config file, with changes needed for your application at runtime.

Example:

RuntimeConfiguration.AddConnectionString("ConnectionString.SQL Server (SqlClient)", 
                "data source=.;initial catalog=Northwind;integrated security=SSPI;persist security info=False;packet size=4096");

To obtain a set connection string for a given key, use the method GetConnectionString(Key).

Dynamic Query Engine configuration

To make the LLBLGen Pro runtime framework be able to generate SQL at runtime, the Dynamic Query Engine (DQE) for the database you'll target has to be setup. All settings available to you for configuration are given below. Some are optional, and some are mandatory.

To be able to configure a DQE, the code calling RuntimeConfiguration needs a reference to the DQE package / dll specific for your database. E.g. if you are targeting SQL Server, you need a reference to the SD.LLBLGen.Pro.DQE.SQLServer.dll / nuget package, as the class used for configuration is present in that package.

The following classes are available for configuration of the different DQEs. Not all DQEs are supported on .NET Standard but as RuntimeConfiguration is usable on .NET full as well, all DQEs offer a configuration class.

  • SQLServerDQEConfiguration, for the SQL Server DQE
  • OracleDQEConfiguration, for the Oracle DQEs.
  • FirebirdDQEConfiguration, for the Firebird DQE
  • MySqlDQEConfiguration, for the MySQL DQE
  • PostgreSQLDQEConfiguration, for the PostgreSQL DQE
  • DB2DQEConfiguration, for the IBM DB2 DQE
  • AccessDQEConfiguration, for the MS Access DQE
  • SpannerDQEConfiguration, for the Google Cloud Spanner DQE

All classes offer a fluent interface to configure all options in one go, using a Lambda:

RuntimeConfiguration.ConfigureDQE<DQEConfigurationClass>(c=>c.Method1(...)
                                                             .Method2(...));

Here c is of type DQEConfigurationClass. See below for examples.

DbProviderFactory

The DbProviderFactory to use for your database is an essential class at runtime, as it is used to create connection objects and the like. On .NET full, the DbProviderFactory to use is in general obtained from the machine.config file of the .NET version you're using, or, in the case where the DbProviderFactory isn't installed in the machine.config file, it's obtained from the config file of your application. On .NET Standard however there's no machine.config file and it's essential that the DbProviderFactory is specified in code using RuntimeConfiguration.

Important!

No ADO.NET provider is available by default, you have to add the nuget package for the particular ADO.NET provider you want to use yourself, in all cases, including SqlClient.

To configure a DbProviderFactory, use the AddDbProviderFactory method on the DQE Configuration class. It's not required to specify an invariant name, it's by default derived from the namespace of the DbProviderFactory type specified.

Example, where the DbProviderFactory of SQLServer is specified:

RuntimeConfiguration.ConfigureDQE<SQLServerDQEConfiguration>(
                c=>c.AddDbProviderFactory(typeof(System.Data.SqlClient.SqlClientFactory)));

This requires your code to have a reference to the ADO.NET provider package of the database you're going to target, because there's no machine.config file nor GAC defined on .NET Standard, the type has to be specified explicitly.

On .NET full, setting a DbProviderFactory using AddDbProviderFactory will overwrite the factory obtained from machine.config/app.config. This means that if you're using ORM Profiler or other profiler which intercepts the ADO.NET provider factories from machine.config/app.config, setting the DbProviderFactory using AddDbProviderFactory will make these profilers to stop working.

Info

For Google Cloud Spanner you always have to register the DbProviderFactory type with the DQE through the RuntimeConfiguration.ConfigureDQE method.

Info

For using the open source Microsoft.Data.SqlClient instead of the System.Data.SqlClient instead, register the factory as below:

RuntimeConfiguration.ConfigureDQE<SQLServerDQEConfiguration>(
                c=>c.AddDbProviderFactory(typeof(Microsoft.Data.SqlClient.SqlClientFactory)));

Tracing

To configure the trace switch specific for the DQE you'll use, (off by default), use the SetTraceLevel method on the DQE configuration class:

Example, where the "SqlServerDQE" trace switch is configured to emit output at the Verbose level.

RuntimeConfiguration.ConfigureDQE<SQLServerDQEConfiguration>(
                                c=>c.SetTraceLevel(TraceLevel.Verbose));

See for more details about trace switches, the Troubleshooting and debugging - Conventions section.

Multi-tenancy support: Catalog name overwriting (SQL Server, MySQL)

Multi-tenancy support is implemented through catalog/schema name overwriting at runtime. This is configurable through code on e.g. the DataAccessAdapter and by using the DQE Configuration class by specifying catalog / schema name overwrites.

Important!

Catalog name overwriting uses case sensitive string comparisons. So if you define a source catalog name of NORTHWIND and the name of the catalog in the metadata (and thus the query) is actually Northwind, no name is overwritten.

For generated code targeting SQL Server or MySQL, it can sometimes be necessary to use the generated code with a catalog with a different name than the catalog the project was created with. As LLBLGen Pro generates the catalog name into the code, it might be necessary to specify a way to rename the catalog name in the generated code at runtime with a given name.

LLBLGen Pro supports multiple catalogs per project, so you can specify more than one rename definition. This feature is similar to the feature discussed in Using the generated code / Adapter / DataAccessAdapter functionality (multi name setting) as it also offers a way to rename multiple catalogs in one go.

To specify a catalog name overwrite use the method AddCatalogNameOverwrite(fromName, toName). See the following example, which specifies a catalog overwrite for two catalogs for SQL Server. To configure it for MySql, you have to use the MySqlDQEConfiguration class instead.

RuntimeConfiguration.ConfigureDQE<SQLServerDQEConfiguration>(
                            c=>c.AddCatalogNameOverwrite("oldCatalogName1", "newCatalogName1")
                                .AddCatalogNameOverwrite("oldCatalogName2", "newCatalogName2"));

This example makes sure that at runtime any reference in the persistence info of the elements in the generated code to the catalog 'OldCatalogName1' is renamed to 'NewCatalogName1' and any reference to 'OldCatalogName2' is renamed to 'NewCatalogName2'.

You can also specify an empty string for the toName. In that case, the DQE will not specify a catalog name in the generated SQL elements, which will make the SQL target the catalog specified in the connection string.

Wildcards

To overwrite all catalog names in the generated code with a single name, you can use the wildcard * to specify the new name of the catalog to use. All catalog names in the generated code will in that case be replaced with the name specified as toName with * as fromName. Specifying * as fromName overrides all other specified catalog name overwrites.

Multi-tenancy support: Schema name overwriting (SQL Server, Oracle, DB2, PostgreSql)

Besides the ability to rename catalog names, offers LLBLGen Pro also the functionality to rename schema names. This is supported for SQL Server, Oracle, PostgreSql and DB2 databases. To specify a schema name overwrite, use the method AddSchemaNameOverwrite(fromName, toName).

Important!

Schema name overwriting uses case sensitive string comparisons. So if you define a source schema name of MYSCHEMA and the name of the schema in the metadata (and thus the query) is actually MySchema, no name is overwritten.

See the following example, which specifies a schema overwrite for two schemas for SQL Server. To specify a schema name overwrite for another database, use the DQE configuration class for that database instead of the SQLServerDQEConfiguration class.

RuntimeConfiguration.ConfigureDQE<SQLServerDQEConfiguration>(
                            c=>c.AddSchemaNameOverwrite("oldSchemaName1", "newSchemaName1")
                                .AddSchemaNameOverwrite("oldSchemaName2", "newSchemaName2"));

This example makes sure that at runtime any reference in the persistence info of the elements in the generated code to the catalog 'OldSchemaName1' is renamed to 'NewSchemaName1' and any reference to 'OldSchemaName2' is renamed to 'NewSchemaName2'.

Important!

Keep in mind that schema renames are global. So you can't rename schema 'dbo' in catalog 'Foo' to 'schema1' and 'dbo' in catalog 'Bar' to 'schema2'.

You can also specify an empty string for the toName. In that case, the schema name isn't generated into the SQL. For some situations this can be handy, though keep in mind that if you use empty strings for schemas on SQL Server, you also have to specify an empty string for the catalog name using AddCatalogNameOverwrite.

Wildcards

To overwrite all schema names in the generated code with a single name, you can use the wildcard * for fromName to specify the new name of the schema to use. All schema names in the generated code will in that case be replaced with the name specified as toName with * as fromName. Specifying * as fromName overrides all other specified schema name overwrites.

SQL Server specific configuration options

The following options are specific for SQL Server.

Set default compatibility level

To use a different default compatibility level of the SQL Server DQE use the method SetDefaultCompatibilityLevel(level):

RuntimeConfiguration.ConfigureDQE<SQLServerDQEConfiguration>(
                                c=>c.SetDefaultCompatibilityLevel(SqlServerCompatibilityLevel.SqlServer2012));

By default the compatibility level is set to: SQL Server 2005+.

Setting this compatibility level is setting the default value which is used in every SQL Server Dynamic Query Engine instance to produce a SQL query. When using Adapter, you can overrule this setting at runtime for a single DataAccessAdapter instance by setting the property CompatibilityLevel of the DataAccessAdapter instance.

See Database specific features, SQL Server Specific: compatibility mode,  for details about the SQL specific effects each compatibility mode has.

Set ArithAbortOn flag

To set the global flag 'ArithAbortOn' you can use either the static field on the DQE's DynamicQueryEngine class, or use the method SetArithAbortOnFlag(value):

RuntimeConfiguration.ConfigureDQE<SQLServerDQEConfiguration>(
                                c=>c.SetArithAbortOnFlag(true));

This sets the ArithAbortOn static field in the SQL Server DQE to the value specified. If true (Default is false) it will signal the DQE to generate SET ARITHABORT ON statements prior to INSERT, DELETE and UPDATE Queries.

PostgreSQL specific configuration options

To following options are specific for PostgreSQL

Use case-insensitive names

To make the DQE generate case insensitive names for tables/fields etc., use the method SetCaseInsensitiveNamesFlag(value):

RuntimeConfiguration.ConfigureDQE<PostgreSQLDQEConfiguration>(
                                c=>c.SetCaseInsensitiveNamesFlag(true));

Default is false.

Oracle specific configuration options

The following options are specific for Oracle. The Oracle DQE for MS Oracle (System.Data.OracleClient) or ODP.NET use the same DQE configuration class OracleDQEConfiguration, so all options are available to both DQEs. Microsoft has deprecated System.Data.OracleClient and you should use ODP.NET instead. The options below are therefore described in relation to ODP.NET.

Trigger based sequence values

Some Oracle schemas use triggers to set the sequence value when a record is inserted. LLBLGen Pro's generated code for Oracle uses a second query to first determine the next value of the sequence (using NEXTVAL as sequence function) then pass that value to the insert query as a normal value.

If that code is then used to insert a row in a table with a trigger for sequence values, the trigger will update the inserted value with a new value, making the value returned to the LLBLGen Pro runtime core not the value the trigger inserted into the sequenced field.

To overcome this, the Oracle DQE can be configured to execute a sequence retrieval query which checks for the current value (using CURRVAL as sequence function) after the insert took place, instead of the sequence retrieval query which checks for the next value before the insert takes place.

To do that, use the following method: SetTriggerSequencesFlag(value):

RuntimeConfiguration.ConfigureDQE<OracleDQEConfiguration>(c=>c.SetTriggerSequencesFlag(true));

Default is false, which will result in a sequence call which asks for the next value before the insert.

Disable ansi-joins

Info

All the DQE's of LLBLGen Pro use ansi-joins by default.

To disable ansi joins (and thus use the FROM A, B, C style joins), use the following method: SetAnsiJoinsFlag(value):

RuntimeConfiguration.ConfigureDQE<OracleDQEConfiguration>(c=>c.SetAnsiJoinsFlag(false));

Default is true, which will result in normal behavior: ansi joins.

Use case-insensitive names

To make the DQE generate case insensitive names for tables/fields etc., use the method SetCaseInsensitiveNamesFlag(value):

RuntimeConfiguration.ConfigureDQE<OracleDQEConfiguration>(
                                c=>c.SetCaseInsensitiveNamesFlag(true));

Default is false.

Always use the unmanaged DbProviderFactory

If you use RuntimeConfiguration on .NET Full, you don't have to specify the DbProviderFactory, the runtime will pick the one associated with the DQE used. For ODP.NET, this is by default the managed provider for ODP.NET, and if it's not available it will switch to the unmanaged provider. In case you want to use the unmanaged provider in all cases (as the unmanaged provider has some limitations like no Xml support), use the following method:

RuntimeConfiguration.ConfigureDQE<OracleDQEConfiguration>(
                                c=>c.SetAlwaysChooseUnmanagedProvider(true));

Default is false.

Set default compatibility level

To use a different default compatibility level of the Oracle DQE use the method SetDefaultCompatibilityLevel(level):

RuntimeConfiguration.ConfigureDQE<OracleDQEConfiguration>(
                                c=>c.SetDefaultCompatibilityLevel(OracleCompatibilityLevel.Oracle12c));

By default the compatibility level is set to: Oracle 9i/10g/11g

Setting this compatibility level is setting the default value which is used in every Oracle Dynamic Query Engine instance to produce a SQL query. When using Adapter, you can overrule this setting at runtime for a single DataAccessAdapter instance by setting the property CompatibilityLevel of the DataAccessAdapter instance.

See Database specific features, Oracle Specific: compatibility mode,  for details about the SQL specific effects each compatibility mode has.

MySql specific configuration options

The following options are specific for MySql

Catalog name inclusion

By default, the catalog name isn't included in queries for MySQL, as versions of LLBLGen Pro before v3 didn't support multiple catalogs for MySQL and enabling it by default would be a breaking change. If you use multiple catalogs in your project for MySQL, you have to enable this setting by using the following method: SetIncludeCatalogNameInObjectNamesFlag(value).

RuntimeConfiguration.ConfigureDQE<MySqlDQEConfiguration>(
                                c=>c.SetIncludeCatalogNameInObjectNamesFlag(true));

Default is false.

Firebird specific configuration options

The following options are specific for Firebird

Set default compatibility level

To use a different default compatibility level of the Firebird DQE use the method SetDefaultCompatibilityLevel(level):

RuntimeConfiguration.ConfigureDQE<FirebirdDQEConfiguration>(
                                c=>c.SetDefaultCompatibilityLevel(FirebirdCompatibilityLevel.Firebird15));

By default the compatibility level is set to: Firebird 2.x

Setting this compatibility level is setting the default value which is used in every Firebird Dynamic Query Engine instance to produce a SQL query. When using Adapter, you can overrule this setting at runtime for a single DataAccessAdapter instance by setting the property CompatibilityLevel of the DataAccessAdapter instance.

It's recommended to leave the compatibility level to the default. Only set it to Firebird15, if you use Firebird 1.5 and you run into problems with the aliases emitted on sub queries inside SELECT statements, which isn't supported on Firebird 1.5.

Trigger based sequence values

Some Firebird databases use triggers to set the sequence value when a record is inserted. LLBLGen Pro's generated code for Firebird uses a second query to first determine the next value of the sequence (using 1 instead of 0 in GEN_ID) then pass that value to the insert query as a normal value.

If that code is then used to insert a row in a table with a trigger for sequence values, the trigger will update the inserted value with a new value, making the value returned to the LLBLGen Pro runtime core not the value the trigger inserted into the sequenced field.

To overcome this, the Firebird DQE can be configured to execute a sequence retrieval query which checks for the current value (using 0 instead of 1 in GEN_ID) after the insert took place, instead of the sequence retrieval query which checks for the next value before the insert takes place.

To do that, use the following method: SetTriggerSequencesFlag(value):

RuntimeConfiguration.ConfigureDQE<FirebirdDQEConfiguration>(c=>c.SetTriggerSequencesFlag(true));

Default is false, which will result in a sequence call which asks for the next value before the insert.

Use case-insensitive names

To make the DQE generate case insensitive names for tables/fields etc., use the method SetCaseInsensitiveNamesFlag(value):

RuntimeConfiguration.ConfigureDQE<FirebirdDQEConfiguration>(
                                c=>c.SetCaseInsensitiveNamesFlag(true));

Default is false.

DB2 specific configuration options

The following options are specific for DB2

Use case-insensitive names

To make the DQE generate case insensitive names for tables/fields etc., use the method SetCaseInsensitiveNamesFlag(value):

RuntimeConfiguration.ConfigureDQE<DB2DQEConfiguration>(
                                c=>c.SetCaseInsensitiveNamesFlag(true));

Default is false.

Dependency Injection configuration

To specify which assemblies the LLBLGen Pro runtime framework should probe for instance types to use in the Dependency Injection system, use the method SetDependencyInjectionInfo(assemblies, instanceTypeFilters) on the RuntimeConfiguration class. It allows you to specify both the assemblies and also the optional instanceTypeFilters:

  • assemblies, an IEnumerable<Assembly> which contains one or more assemblies to probe for DependencyInjectionInfoAttribute annotated types (Instance types)
  • instanceTypeFilters, optional IEnumerable<string> (specify null if you don't need filters). If no filters are specified, all instance types are enabled. If you specify one or more instance type filters, only the instance type filters matching the filter will be enabled. The specified namespace fragment means that you can specify the first part of a namespace to get a match. So if you specify "Northwind", both the namespaces "Northwind.DAL" and "Northwind.BL" will match but "GUI.Northwind" will not. You can use instance filters if you have multiple groups of instance types defined in the assembly or assemblies specified and you only want to use a subset of them.

In the following example, the assembly containing the type MyValidator is specified as the assembly to probe for instance types. Additionally, two type filters are specified, "Namespace1" and "Namespace2".

RuntimeConfiguration
        .SetDependencyInjectionInfo(new List<Assembly>() { typeof(MyValidator).Assembly },
                                    new List<string>() { "Namespace1", "Namespace2" });
Info

There's no auto-discovery setting on RuntimeConfiguration as it's in general better to specify the assemblies to probe instead of letting the runtime probe all assemblies reachable at runtime, which could harm startup time.

Configuration of external Dependency Injection framework

To use an external Dependency Injection framework, please see the section Dependency Injection support for other DI frameworks. After configuring the DI framework in asp.net core, use the SetDependencyInjectionInfo method to specify the types with the RuntimeConfiguration class. In the following example the injection for an auditor and an authorizer are setup by specifying lambda's how to obtain them from the IServiceProvider instance.

// services is the services argument in asp.net core's ConfigureServices method
RuntimeConfiguration.SetDependencyInjectionInfo(services, 
        (s, e)=>e.AuditorToUse=(IAuditor)s.GetService(typeof(IAuditor)), 
        (s, e) => e.AuthorizerToUse=(IAuthorizer)s.GetService(
                typeof(IMyAuthorizer<>).MakeGenericType(e.GetType())));

Tracing configuration

The LLBLGen Pro Runtime Framework has a variety of Trace Switches which can be configured using the RuntimeConfiguration class. The DQE related trace switches are configured using the Dynamic Query Engine configuration classes, see above. The other trace switches are configured using the method SetTraceLevel( switchName, level) on the property Tracing.

Example, which defines the level of two trace switches:

RuntimeConfiguration.Tracing
                        .SetTraceLevel("ORMPersistenceExecution", TraceLevel.Info);
                        .SetTraceLevel("ORMPlainSQLQueryExecution", TraceLevel.Info);

You can also use the ORMTraceSwitchNames helper to define the names:

RuntimeConfiguration.Tracing
                        .SetTraceLevel(ORMTraceSwitchNames.ORMPersistenceExecution, TraceLevel.Info);
                        .SetTraceLevel(ORMTraceSwitchNames.ORMPlainSQLQueryExecution, TraceLevel.Info);
Tip

On .NET Core, if you want to receive tracing info in the console, use this tracer setup:

// add at the start of your program
Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));

After that, info emitted by the enabled tracers inside the LLBLGen Pro Runtime Framework will be shown on the console. You can also use this to e.g. show the trace output of unit tests with a unit test.

Prefetch path configuration

To specify the default value for the UseRootMaxLimitAndSorterInPrefetchPathSubQueries static property on Prefetch paths, use the following method on the Prefetching property of the RuntimeConfiguration class:

RuntimeConfiguration.Prefetching
                        .SetUseRootMaxLimitAndSorterInPrefetchPathSubQueriesDefault(true);

Default is false. Use this method with care, in general it's not recommended to set this flag to another value than its default.

Xml Serialization configuration

To specify the culture to use for serializing and deserializing System.Single, System.Double and System.Decimal typed values to and from XML, use the following method on the Xml property of the RuntimeConfiguration class: SetCultureNameForXmlValueConversion(name).

Example, which sets the culture to 'en-US':

RuntimeConfiguration.Xml.SetCultureNameForXmlValueConversion("en-US");

You can specify either the empty string, which will result in the Invariant culture, or the the name of the culture to use, e.g. nl-NL or en-US. For the full list of iso culture names please see the CultureInfo class documentation in the MSDN.

Entity Behavior configuration

To configure entity and entity handling behavior of the runtime, you can use various settings, they're described below. All these methods are available on the Entity property of the RuntimeConfiguration class. The methods on Entity form a fluent interface, so you can chain methods together.

Mark an entity as 'fetched' after save

To automatically mark a saved entity as 'Fetched', use the method SetMarkSavedEntitiesAsFetched(value):

RuntimeConfiguration.Entity.SetMarkSavedEntitiesAsFetched(true);

Default is false.

See for more information Using the entity classes - Setting the EntityState to Fetched automatically after a save: for adapter and for selfservicing.

Bypass built-in validation

To bypass build-in validation logic in favor for your own validation logic, use the method SetBuildInValidationBypassMode(mode).

RuntimeConfiguration.Entity
            .SetBuildInValidationBypassMode(BuildInValidationBypass.BypassWhenValidatorPresent);

Default is NoBypass.

See for more information: Validation per field or per entity - Bypassing build-in validation logic

Define Scale overflow correction action

To define the correction action for a Scale overflow error, use the method SetScaleOverflowCorrectionActionToUse(value).

RuntimeConfiguration.Entity
            .SetScaleOverflowCorrectionActionToUse(ScaleOverflowCorrectionAction.Round);

Default is Truncate.

See for more information: Validation per field or per entity - Defining the Scale overflow correction action to use

Specify string hashcode behavior

To specify that the LLBLGen Pro runtime should use case insensitive hashcodes for strings (the hashcode for "Foo" is the same as for "FoO"), use the following method: SetCaseSensitiveStringHashCodes(value).

RuntimeConfiguration.Entity.SetCaseSensitiveStringHashCodes(true);

Default is false, which means hashcodes are case sensitive. Hashcodes for string values in entity fields are used in prefetch path mergers and for example projection distinct filtering.

In general you should keep this setting set to the default (true). Only set this to false if you're working with a case-insensitive database which has FK values which differ only in casing from their PK counterparts (e.g. the FK field value is "Foo" and the PK field value is "foo").

Specify non-nullable field set to null behavior

By default, setting a nonnullable field to null / nothing is silently resulting in a no-op, without any exception. However it can be beneficial to have this as a mandatory validation option. To make the runtime throw an ArgumentOutOfRangeException, use the method SetMakeSettingNonNullableFieldsToNullFatal(value).

RuntimeConfiguration.Entity.SetMakeSettingNonNullableFieldsToNullFatal(true);

Default is false.

Allow reads from deleted entities

By default reading a value from an entity object which contains an entity which was deleted will result in an ORMEntityIsDeletedException. If you want to allow reads from deleted entities however, use the method: SetAllowReadsFromDeletedEntities(value)

RuntimeConfiguration.Entity.SetAllowReadsFromDeletedEntities(true);

Default is false.

Usage example 1 with data from appsettings.json

The following example, supplied by Kai Wadsack, shows how to consume environment variables and the appsettings.json file to read information which is then used to configure the LLBLGen Pro Runtime Framework using the RuntimeConfiguration system.

The appsettings.json file:

{
    "LLBLGen":{
        "ConnectionStrings": {
            "ConnectionString.SQL Server (SqlClient)": "data source=YOURDATABOX;initial catalog=SomeUser;User ID=user;Password=secret;persist security info=False;packet size=4096;Connection Timeout=300"
        },
        "SqlServerCatalogNameOverwrites": [
            {
                "CatalogName": "NewName",
                "Overwrite": ""
            }
        ],
        "Tracing": {
            "Switches": {
                "SqlServerDQE": 0,
                "ORMGeneral": 0,
                "ORMStateManagement": 0,
                "ORMPersistenceExecution": 0,
                "ORMPlainSQLQueryExecution": 0
            },
            "Listeners": {
                "Debug": false,
                "Console": true,
                "File": "TraceLog.log"
            }
        }
    }
}

The code to parse the above file and set the various RuntimeConfiguration settings:

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Linq;

using Microsoft.Extensions.Configuration;

using SD.LLBLGen.Pro.DQE.SqlServer;

namespace MyCompany.MyApp {
    public static class ConfigurationHelper {
        
        public static void ConfigureFromAppSettings() {
            var environmentName = Environment.GetEnvironmentVariable("ASPNET_ENV");

            if (string.IsNullOrEmpty(environmentName)) {
                environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
            }

            if (string.IsNullOrEmpty(environmentName)) {
                environmentName = "Production";
            }

            var config = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", true, true)
                .AddJsonFile($"appsettings.{environmentName}.json", true, true)
                .AddJsonFile($"appsettings.{Environment.MachineName}.json", true, true)
                .Build();

            ConfigureFromConfiguration(config);
        }

        public static void ConfigureFromJson(string configFileName) {
            var configDirectory = Directory.GetCurrentDirectory();
            ConfigureFromJson(configFileName, configDirectory);
        }

        public static void ConfigureFromJson(string configFileName, string configDirectory) {
            var configuration = new ConfigurationBuilder()
                .SetBasePath(configDirectory)
                .AddJsonFile(configFileName, true, true)
                .Build();

            ConfigureFromConfiguration(configuration);
        }

        private static void ConfigureFromConfiguration(IConfiguration configuration) {

            var llblGenSettings = configuration.GetSection("LLBLGen");

            var traceSettings =
                llblGenSettings.GetSection("Tracing");

            List<IConfigurationSection> traceSwitches =
                traceSettings.GetSection("Switches").GetChildren().ToList();

            var traceListeners =
                traceSettings.GetSection("Listeners");

            List<IConfigurationSection> connectionStrings =
                configuration.GetSection("ConnectionStrings").GetChildren().ToList();

            if (!connectionStrings.Any()) {
                connectionStrings =
                    llblGenSettings.GetSection("ConnectionStrings").GetChildren().ToList();
            }

            List<IConfigurationSection> catalogNameOverwrites =
                llblGenSettings.GetSection("SqlServerCatalogNameOverwrites").GetChildren().ToList();

            var isTraceEnabled = false;

            foreach (var traceSwitch in traceSwitches.Where(s => s.Key != "SqlServerDQE")) {
                if (!int.TryParse(traceSwitch.Value, out var value)) {
                    continue;
                }

                if (value > 0) {
                    isTraceEnabled = true;
                }

                RuntimeConfiguration.Tracing
                    .SetTraceLevel(traceSwitch.Key, (TraceLevel)value);
            }

            foreach (var connection in connectionStrings) {
                RuntimeConfiguration.AddConnectionString($"{connection.Key}", connection.Value);
            }

            RuntimeConfiguration.ConfigureDQE<SQLServerDQEConfiguration>(config => {
                config
                    .AddDbProviderFactory(typeof(SqlClientFactory))
                    .SetDefaultCompatibilityLevel(SqlServerCompatibilityLevel.SqlServer2012);

                var dqeTraceSetting =
                    traceSwitches.FirstOrDefault(s => s.Key == "SqlServerDQE");

                if (dqeTraceSetting != null) {
                    if (int.TryParse(dqeTraceSetting.Value, out var value)) {
                        if (value > 0) {
                            isTraceEnabled = true;
                        }
                        config.SetTraceLevel((TraceLevel)value);
                    }
                }

                foreach (var catalogNameOverwrite in catalogNameOverwrites) {
                    var catalogName =
                        catalogNameOverwrite.GetChildren()?.FirstOrDefault(s => s.Key == "CatalogName")?.Value;

                    if (catalogName == null) {
                        continue;
                    }

                    var overwrite =
                        catalogNameOverwrite.GetChildren()?.FirstOrDefault(s => s.Key == "Overwrite")?.Value;

                    config.AddCatalogNameOverwrite(catalogName, overwrite ?? string.Empty);
                }

            });

            if (!isTraceEnabled || !traceListeners.GetChildren().Any()) {
                return;
            }

            var logToConsole = traceListeners.GetValue<bool>("Console");
            var logToDebug = traceListeners.GetValue<bool>("Debug");
            var logFileName = traceListeners.GetValue<string>("File");

            Trace.Listeners.Clear();
    
            if (logToConsole) {
                Trace.Listeners.Add(new TextWriterTraceListener(Console.Out, "Console"));
            }

            if (logToDebug) {
                Trace.Listeners.Add(new DefaultTraceListener {
                    Name = "Debug",
                    LogFileName = logFileName
                });
                return;
            }

            if (string.IsNullOrEmpty(logFileName)) {
                Trace.Listeners.Add(new TextWriterTraceListener(logFileName, "File"));                
            }
        }
    }
}

Usage example 2 with data from appsettings.json

The following example, supplied by Wayne Brantley, shows how to consume configuration settings from the appsettings.json file and fill a simple configuration class to configure the LLBLGen Pro Runtime Framework using the RuntimeConfiguration system. The example uses SQL Server but is easy to change to your database of choice.

The appsettings.json file:

"LLBLGen": {
    "ConnectionStrings": {
     "Main.ConnectionString.SQL Server (SqlClient)": "data source=.;;initial catalog=Xyz;",
     "Other.SQL": "data source=.;initial catalog=Other;"
    },
    "SqlServerCatalogNameOverwrites": [
     {
        "CatalogName": "",
        "Overwrite": ""
     }
    ],
    "Tracing": {
     "Switches": {
        "SqlServerDQE": "Off",
        "ORMGeneral": "Error",
        "ORMStateManagement": "Off",
        "ORMPersistenceExecution": "Off",
        "ORMPlainSQLQueryExecution": "Off"
     },
     "Listeners": {
        "Debug": false,
        "Console": true,
        "File": "TraceLog.log"
     }
    }
}

This data is read into a configuration class called LlblgenOptions

   public class LlblgenOptions
    {
        public Dictionary<string, string> ConnectionStrings { get; set; } = new Dictionary<string, string>();
        public List<LlblgenSqlServerCatalogNameOverwritesOption> SqlServerCatalogNameOverwrites { get; set; } = 
            new List<LlblgenSqlServerCatalogNameOverwritesOption>();

        public LlblgenTracingOptions Tracing { get; set; } = new LlblgenTracingOptions();
        public class LlblgenTracingOptions
        {
            public LlblgenTracingSwitchOptions Switches { get; set; } = new LlblgenTracingSwitchOptions();
            public LlblgenTracingListenerOptions Listeners { get; set; } = new LlblgenTracingListenerOptions();
            public class LlblgenTracingSwitchOptions
            {
                public TraceLevel SqlServerDQE { get; set; }
                public TraceLevel ORMGeneral { get; set; }
                public TraceLevel ORMStateManagement { get; set; }
                public TraceLevel ORMPersistenceExecution { get; set; }
                public TraceLevel ORMPlainSQLQueryExecution { get; set; }
            }
            public class LlblgenTracingListenerOptions
            {
                public bool Debug { get; set; }
                public bool Console { get; set; }
                public string File { get; set; }
            }
        }
        public class LlblgenSqlServerCatalogNameOverwritesOption
        {
            public string CatalogName { get; set; }
            public string Overwrite { get; set; }
        }
    }

To read the data into an instance of LlblgenOptions, the following code is used: (Bind() is an extension method from the Microsoft.Extensions.Configuration package)

// e.g. in ConfigureServices
var options = new LlblgenOptions();
configuration.Bind(options);

An instance of LlblgenOptions is then passed to this method:

public void OrmConfig(LlblgenOptions options)
{
    foreach (var connectionString in options.ConnectionStrings)
    {
        RuntimeConfiguration.AddConnectionString(connectionString.Key, connectionString.Value);
    }
    
    RuntimeConfiguration.Tracing.SetTraceLevel(nameof(options.Tracing.Switches.ORMGeneral), 
        options.Tracing.Switches.ORMGeneral);
    RuntimeConfiguration.Tracing.SetTraceLevel(nameof(options.Tracing.Switches.ORMPersistenceExecution),
        options.Tracing.Switches.ORMPersistenceExecution);
    RuntimeConfiguration.Tracing.SetTraceLevel(nameof(options.Tracing.Switches.ORMPlainSQLQueryExecution),
        options.Tracing.Switches.ORMPlainSQLQueryExecution);
    RuntimeConfiguration.Tracing.SetTraceLevel(nameof(options.Tracing.Switches.ORMStateManagement), 
        options.Tracing.Switches.ORMStateManagement);

    RuntimeConfiguration.ConfigureDQE<SQLServerDQEConfiguration>(config =>
    {
        config.AddDbProviderFactory(typeof(SqlClientFactory))
             .SetDefaultCompatibilityLevel(SqlServerCompatibilityLevel.SqlServer2012)
             .SetTraceLevel(options.Tracing.Switches.SqlServerDQE);
        foreach (var catalogNameOverwrite in options.SqlServerCatalogNameOverwrites)
        {
            config.AddCatalogNameOverwrite(catalogNameOverwrite.CatalogName, catalogNameOverwrite.Overwrite);
        }
    });

    bool traceEnabled = options.Tracing.Switches.ORMGeneral != TraceLevel.Off
                        || options.Tracing.Switches.ORMPersistenceExecution != TraceLevel.Off
                        || options.Tracing.Switches.ORMPlainSQLQueryExecution != TraceLevel.Off
                        || options.Tracing.Switches.ORMStateManagement != TraceLevel.Off
                        || options.Tracing.Switches.SqlServerDQE != TraceLevel.Off;

    if (traceEnabled)
    {
        if (options.Tracing.Listeners.Console)
        {
            Trace.Listeners.Add(new TextWriterTraceListener(Console.Out, "Console"));
        }
        if (options.Tracing.Listeners.Debug)
        {
            Trace.Listeners.Add(new DefaultTraceListener { Name = "Debug", LogFileName = options.Tracing.Listeners.File });
        }
        else 
        {
            if (!string.IsNullOrEmpty(options.Tracing.Listeners.File))
            {
                Trace.Listeners.Add(new TextWriterTraceListener(options.Tracing.Listeners.File, "File"));
            }
        }
    }
}