4

I have an ASP.NET Core application running as a Windows Service. Due to project requirements, I am using Entity Framework v6.3 (as opposed to using EF Core).

I am having trouble retrieving the correct connection string when performing a migration and also upon publishing the service to a server. When running the service locally, the connection string is retrieved successfully.

As I understand it, Entity Framework 6 is supposed to retrieve connection strings from web.config or app.config files, right? Therefore, I have an app.config file containing two connection strings, e.g.

<connectionStrings>
    <add name="DataContext" providerName="System.Data.SqlClient" connectionString="Server=localhost\SQLEXPRESS;[redacted]" />
    <add name="CrmContext" providerName="System.Data.SqlClient" connectionString="Server=localhost\SQLEXPRESS;[redacted]" />
</connectionStrings>

In my Startup class, I have registered both database contexts, e.g.

services.AddTransient<DataContext>();
services.AddTransient<CrmContext>();

On a Razor page, when I instantiate both data contexts on my local machine I can see the correct connection string is being resolved for both contexts (by using _crmContext.Database.Connection.ConnectionString).

When I attempt to add a migration using the update-database command on my CrmContext (automatic migrations enabled), the correct connection string isn't resolved. Instead, it defaults to creating a LocalDB database using the default connection string: (localdb)\MSSQLLocalDB

How come it isn't using the connection string I have provided in my app.config file? I also a web.config file but it doesn't resolve from there either.

CrmContext.cs

public class CrmContext : DbContext
{
    public CrmContext() : base("CrmContext")
    {
        Database.SetInitializer<CrmContext>(null);
    }

    public IDbSet<CustomerDetails> CustomerDetails { get; set; }
}

CrmConfiguration.cs

internal sealed class CrmConfiguration : DbMigrationsConfiguration<CrmContext>
{
    public CrmConfiguration()
    {
        AutomaticMigrationsEnabled = true;
    }

    protected override void Seed(CrmContext context)
    {
        ...

        context.SaveChanges();
    }
}

I've tried to explicitly update the CRM connection by specifying my CrmConfiguration file:

update-database -ConfigurationTypeName CrmContext

When I do this, it creates and updates the LocalDB database instead of using my connection string.

I've also tried to explicitly referencing the connection string:

update-database -ConnectionStringName "CrmContext"

This results in this error:

No connection string named 'CrmContext' could be found in the application config file.

My CrmContext class exists within my ASP.NET Core windows application where my DataContext class exists in a separate 'Data' project (as it's shared with an ASP.NET MVC v5 application)

enter image description here

enter image description here

When I publish my service and install the application as a Windows Service, I found that it also doesn't pick up the connection strings at all from any of the config files - it again just looks for the default localdb database. As I understand, it should pick it up from my PaymentBatchProcessorService.exe.config file, right?

My PaymentBatchProcessorService.exe.config file looks like this:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <connectionStrings>
    <add name="DataContext" providerName="System.Data.SqlClient" connectionString="redacted" />
    <add name="CrmContext" providerName="System.Data.SqlClient" connectionString="redacted" />
  </connectionStrings>
</configuration>

enter image description here

As per Microsoft documentation, it should be possible to load in the additional XML configuration files via the following code in the Program.cs file, but EntityFramework still didn't pick up the connection strings:

Host.CreateDefaultBuilder(args)
    .UseWindowsService()
    .ConfigureAppConfiguration((hostingContext, config) =>
    {
        var workingDirectoryPath = Environment.GetEnvironmentVariable(EnvironmentVariables.ServiceWorkingDirectory);

        config.AddXmlFile(Path.Combine(workingDirectoryPath, "app.config"));
        config.AddXmlFile(Path.Combine(workingDirectoryPath, "web.config"));
    })

e.g. in the above sample, the working directory path resolves to the location where my .exe is for the Windows Service.

Thanks for reading this far!

Ciaran Gallagher
  • 3,653
  • 7
  • 47
  • 90

1 Answers1

0

When you deploy your service, the .config file should instead be called servicename.exe.config. It should reside on the same folder where the service was registered with installutil. See Windows service app.config location.

Ricardo Peres
  • 11,795
  • 4
  • 45
  • 64
  • Thanks, I gave that a try by renaming the app.config to PaymentBatchProcessorService.exe.config and double-checking it's contents but unfortunately it still didn't pick up the connection strings. – Ciaran Gallagher Feb 17 '20 at 11:14
  • It gave error: "System.InvalidOperationException: No connection string named 'DataContext' could be found in the application config file." Which is strange to me because it's definitely there in the same folder – Ciaran Gallagher Feb 17 '20 at 11:16
  • See the other question I posted. It definitely should be there. – Ricardo Peres Feb 17 '20 at 11:17
  • When I publish it, it generates an "app.config" file. When I rename that file to include the service name as you suggested it still doesn't pick it up for some reason. I know when I publish a command line program with .NET Framework it correctly names the config file with the exe name (e.g. JobRunner.exe.config). Not sure why it doesn't do it for my .NET Core service. – Ciaran Gallagher Feb 17 '20 at 11:24
  • I had forgotten it was a .NET Core service, I don't think that really applies. Are you using ConfigurationManager? If so, can you call its OpenExeConfiguration with the local path to the .config file? See https://docs.microsoft.com/en-us/dotnet/api/system.configuration.configurationmanager.openexeconfiguration – Ricardo Peres Feb 17 '20 at 11:29
  • Unfortunately I have no control over how it picks up the configuration file. I'm using Entity Framework 6.3. The connection string either has to be placed directly in the base constructor of the database context or you provide the name of the data context which it is supposed to pick up from app.config as per official documentation: https://docs.microsoft.com/en-us/ef/ef6/fundamentals/configuring/connection-strings – Ciaran Gallagher Feb 17 '20 at 11:45
  • 1
    Yes, I was thinking that you could pass the connection string as obtained from the ConfigurationManager. – Ricardo Peres Feb 17 '20 at 11:46
  • In my DataContext.cs file of my referenced Data project, I tried ConfigurationManager.OpenExeConfiguration and also tried ConfigurationManager.ConnectionStrings["DataContext"].ConnectionString but both resulted in a null reference exception unfortunately. – Ciaran Gallagher Feb 17 '20 at 11:58