ConnectionString Change, Only for Entlib5 Logging

Topics: Logging Application Block
Oct 31, 2012 at 3:40 AM
Edited Oct 31, 2012 at 3:41 AM

 

I have a very large/complex setup in app.config with multiple logging settings. Depending on severity, log messages are written to Event Log, database, rolling file, web service.

I now have a a need to make a dynamic connection string change for the database, only, at runtime. This is to support a high availability situation.

 

How can I pass-in a connectionstring to the Enterprise Library Logging Framework, dynamically and programatically, without overridding any other settings, and without changing the connection string in the file?

I am using .NET 4.5, C#, VS 2012 of course.

Thanks.

Oct 31, 2012 at 6:48 AM
Edited Oct 31, 2012 at 7:22 AM

I'm assuming that by dynamic at runtime you mean that at any point an event occurs (e.g. database goes down) and at that point you want to change the database connection string.  Just for the record, in my experience this type of behavior is typically handled by virtual IP addresses and clustering.

However, if you want to do something similar via software then there are 2 ways I can think of: change the connection string from the retrieved database or modify the container with the new database connection string.

You could always retrieve the Database instance and then create a new instance with  the new connection string (since ConnectionString is readonly):

var db = EnterpriseLibraryContainer.Current.GetInstance<Database>("ApplicationDB");
var dbCtr = db.GetType().GetConstructor(new Type[] { typeof(string) });
db = (Database)dbCtr.Invoke(new object[] { @"data source=.\SQLEXPRESS;Database=MYNEWDB;Integrated Security=SSPI;User Instance=false" });

You would probably hide this behind a helper method or wrapper class.  Probably a better approach would be to replace the registration in the container.  You could do this by using Unity directly:

IUnityContainer container = new UnityContainer();

// Load the default configuration information from the config file
// i.e. ConfigurationSourceFactory.Create()
// and add it to the container
container.AddNewExtension<EnterpriseLibraryCoreExtension>();

// Create a configurator to use to configure our fluent configuration
var configurator = new UnityContainerConfigurator(container);

// Use the configured container with file and fluent config as the Enterprise Library service locator
EnterpriseLibraryContainer.Current = new UnityServiceLocator(container);

// Original DB
var db = EnterpriseLibraryContainer.Current.GetInstance<Database>("ApplicationDB");

// Register new DB Connection
container.RegisterType(typeof(Database), db.GetType(), "ApplicationDB",
    new InjectionConstructor(@"data source=.\SQLEXPRESS;Database=MYNEWDB;Integrated Security=SSPI;User Instance=false"));

// Retrieve the new DB
db = EnterpriseLibraryContainer.Current.GetInstance<Database>("ApplicationDB");

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com 

Nov 1, 2012 at 2:33 AM
Edited Nov 1, 2012 at 2:42 AM


Thanks for the fast response. I selected the second option. I removed the reference to the database in app.config so I could construct the connectionstring/daabase purely in code. The code then breaks. The code you provided does work if I keep the connection string in the app.config. I need the connection strings out of the app.config and change them on the fly as needed.

 

 

The exception raised:

Resolution of the dependency failed, type = "Microsoft.Practices.EnterpriseLibrary.Logging.LogWriter", name = "(none)".
Exception occurred while: while resolving.
Exception is: InvalidOperationException - The type Database cannot be constructed. You must configure the container to supply this value.
-----------------------------------------------
At the time of the exception, the container was:

  Resolving Microsoft.Practices.EnterpriseLibrary.Logging.LogWriterImpl,LogWriter.__default__ (mapped from Microsoft.Practices.EnterpriseLibrary.Logging.LogWriter, (none))
  Resolving parameter "structureHolder" of constructor
  ........
  Resolving parameter "database" of constructor Logging.FormattedDatabaseTraceListener(Microsoft.Practices.EnterpriseLibrary.Data.Database database, System.String writeLogStoredProcName, System.String addCategoryStoredProcName, Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.ILogFormatter formatter)
            Resolving Microsoft.Practices.EnterpriseLibrary.Data.Database,Host.Db.Connection.LoggingDatabase


My code is


    IUnityContainer container = new UnityContainer();
    container.AddNewExtension<EnterpriseLibraryCoreExtension>();
    var configurator = new UnityContainerConfigurator( container );
    EnterpriseLibraryContainer.Current = new UnityServiceLocator( container );
    var connString = definedsomewhereelse;
    Database logDb = new SqlDatabase( connString );
    container.RegisterType( typeof( Database ) , logDb.GetType() , "LoggingDatabase" , new InjectionConstructor( connString ) );
    try
    {

        logDb = EnterpriseLibraryContainer.Current.GetInstance<Database>( "LoggingDatabase" );
            var logEntry = new LogEntry
            {
                EventId = 4 ,
                Priority = 1 ,
                Severity = logseverity ,
                Title = logtitle,
                Message = logmessage
            };
            logEntry.Categories.Add( paramvalue );
            var logWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();   // THROWS HERE
            logWriter.Write( logEntry );
    }
            catch ( Exception ex )
            { }


Nov 1, 2012 at 4:39 AM
Edited Nov 1, 2012 at 4:40 AM

I'm assuming that you are configuring the Logging Application Block via configuration file.  I think you will need to replace the entire configuration because the original Database is already injected into the logging block.  

So given the following configuration:

 

<?xml version="1.0"?>
<configuration>
<configSections>
    <section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
    <section name="dataConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
</configSections>
<loggingConfiguration name="" tracingEnabled="true" defaultCategory="General">
    <listeners>
        <add name="Database Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.Database.FormattedDatabaseTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging.Database, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
            listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Database.Configuration.FormattedDatabaseTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging.Database, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
            databaseInstanceName="LoggingDb" writeLogStoredProcName="WriteLog"
            addCategoryStoredProcName="AddCategory" />
        <add name="Flat File Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FlatFileTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
            listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FlatFileTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
            fileName="trace.log" />
    </listeners>
    <formatters>
        <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
            template="Timestamp: {timestamp}{newline}&#xA;Message: {message}{newline}&#xA;Category: {category}{newline}&#xA;Priority: {priority}{newline}&#xA;EventId: {eventid}{newline}&#xA;Severity: {severity}{newline}&#xA;Title:{title}{newline}&#xA;Machine: {localMachine}{newline}&#xA;App Domain: {localAppDomain}{newline}&#xA;ProcessId: {localProcessId}{newline}&#xA;Process Name: {localProcessName}{newline}&#xA;Thread Name: {threadName}{newline}&#xA;Win32 ThreadId:{win32ThreadId}{newline}&#xA;Extended Properties: {dictionary({key} - {value}{newline})}"
            name="Text Formatter" />
    </formatters>
    <categorySources>
        <add switchValue="All" name="General">
            <listeners>
                <add name="Database Trace Listener" />
            </listeners>
        </add>
    </categorySources>
    <specialSources>
        <allEvents switchValue="All" name="All Events" />
        <notProcessed switchValue="All" name="Unprocessed Category" />
        <errors switchValue="All" name="Logging Errors &amp; Warnings">
            <listeners>
                <add name="Flat File Trace Listener" />
            </listeners>
        </errors>
    </specialSources>
</loggingConfiguration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>

 

notice that the logging databaseInstanceName is configured but no actual database is specified in the configuration file.  If you just tried to run without specifying the database information programmatically then an ActivationException will be raised.  The code to define the database and register it with the container looks like this:

 

void ConfigureContainer(string name, string connectionString)
{
    var builder = new ConfigurationSourceBuilder();

    builder.ConfigureData()
            .ForDatabaseNamed(name)
                .ThatIs.ASqlDatabase()
                .WithConnectionString(connectionString)
                .AsDefault();

    var configSource = new DictionaryConfigurationSource();
    builder.UpdateConfigurationWithReplace(configSource);

    IUnityContainer container = new UnityContainer();

    // Load the default configuration information from the config file
    // i.e. ConfigurationSourceFactory.Create()
    // and add it to the container
    container.AddNewExtension<EnterpriseLibraryCoreExtension>();

    // Create a configurator to use to configure our fluent configuration
    var configurator = new UnityContainerConfigurator(container);
    EnterpriseLibraryContainer.ConfigureContainer(configurator, configSource);

    // Use the configured container with file and fluent config as the Enterprise Library service locator
    EnterpriseLibraryContainer.Current = new UnityServiceLocator(container);
}

 

In this example I'm using the Fluent API to configure the database information and merging that with the configuration file based logging information.  Notice that the database name in the code is the same as in the configuration file.

 

    ConfigureContainer("LoggingDb", @"data source=.\SQLEXPRESS;Database=LoggingDefault;Integrated Security=SSPI");

    var logWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();
    logWriter.Write("Test", "General");

    // Change the database!
    ConfigureContainer("LoggingDb", @"data source=.\SQLEXPRESS;Database=Logging;Integrated Security=SSPI");

    logWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();
    logWriter.Write("Test", "General");

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com 

Nov 1, 2012 at 3:39 PM

Perfect. Thank you very, very much. That solves it. It appears to be a lot of code to change a connection string, but I think the EntLib was not exactly designed for this situatio, but it works well.

 

Thanks again.

May 23, 2013 at 3:47 PM
Hi
We are using entlib 6.0 and we have same requirement i see enterpriselibrarycontainer is not present in it,how can we accomplish the same feature in entlib6.0,i need to pass this databaseinfo into different moduless via unity in prism wpf app.need your inputs
May 23, 2013 at 4:42 PM
Edited May 23, 2013 at 6:23 PM
If you are using programmatic configuration (which is now the preferred approach) then it should be fairly straight forward. The steps would be to configure Enterprise Library programmatically and then to register those types/objects with Unity. If you need to reconfigure then repeat with the new configuration.

If you need more details, then I would recommend posting a new question with your specific scenario. Thanks.

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to
May 24, 2013 at 4:43 AM
Could you please send me the sample of how do we do using programmatic configuration.
It would be helpful

Krishna
May 24, 2013 at 1:30 PM
Are you just using the Data Access Application Block or some other blocks as well?

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to
May 24, 2013 at 5:00 PM
Hi ,
We are using entlib6.0 dataaccess application block, we have a wpf prism app which has mvvmapproach and we have a DAL layer wherein we use ORM maping , we have two connection strings in app.config of shell , in the bootstrapper we wanted to create / register a class like connection info which has two properties i.e connection strings , i want to assign the connection strings from app.confif and pass to the construtor of DAL layers.need the sample code for doing it ,in bootstrapper