1

THE GOAL
I'm trying to have a class library shared between an ASP.NET Core web app and other projects/solutions and the class should be able to interact with whatever database is being used by the calling process/environment.

THE PROBLEM
I have a class library that is throwing up a weird error when used in my ASP.NET Core 3.1 web app. The class library is actually shared between the front end (the website) and the backend app that takes care of some recurring, heavy load processes. I'm using EF Core with both front and back ends and the database is on Azure, not my local machine. Yet, when the web app tries to do some work I am getting the following error:

An attempt to attach an auto-named database for file C:...\bin\Debug\netcoreapp3.1\aspnetdb.mdf failed. A database with the same name exists, or specified file cannot be opened, or it is located on UNC share.

This doesn't make sense to me at all since the DB is on Azure. Also, calling the same exact method in the library using the backend app doesn't throw up this error. The connection string is stored in appsettings.json for the website and app.config for the backend.

This is block of code that is throwing the error, but again this is only happening on the ASP.NET Core project on the SaveChanges() call:

public static void AddLogEvent(int Severity, DateTime EventTime, string EventType, string User, string Message)
{
        DBEntities context = new DBEntities();
        DbSet<LogEvent> dbSet = context.Set<LogEvent>();
        LogEvent NewRecord = new LogEvent();
        NewRecord.Id = Guid.NewGuid();
        NewRecord.Severity = Severity;
        NewRecord.EventTime = EventTime;
        NewRecord.EventType = EventType;
        NewRecord.User = User;
        NewRecord.Message = Message;

        dbSet.Add(NewRecord);

        context.SaveChanges();
}

Within DBEntities, I am overriding the OnConfiguring() method to ensure proper connection for whatever environment is making the call as such:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
        if (!optionsBuilder.IsConfigured)
        {
            bool FoundValidConnection = false;

            if (ConfigurationManager.ConnectionStrings.Count > 0)
            {
                foreach (ConnectionStringSettings connstr in ConfigurationManager.ConnectionStrings)
                {
                    if (FoundValidConnection == false)
                    {
                        if (string.Compare(connstr.Name.Trim().ToUpper(), "DefaultConnection".ToUpper()) == 0)
                        {
                            optionsBuilder.UseSqlServer(connstr.ConnectionString);
                            FoundValidConnection = true;
                            break;
                        }
                    }
                }

                foreach (ConnectionStringSettings connstr in ConfigurationManager.ConnectionStrings)
                {
                    if (FoundValidConnection == false)
                    {
                        if (string.Compare(connstr.Name.Trim().ToUpper(), "DBEntities".ToUpper()) == 0)
                        {
                            optionsBuilder.UseSqlServer(connstr.ConnectionString);
                            FoundValidConnection = true;
                            break;
                        }
                    }
                }

                if (FoundValidConnection == false)
                {
                    //if still haven't found one of the expected connection string names, then take whatever the first one is.
                    optionsBuilder.UseSqlServer(ConfigurationManager.ConnectionStrings[0].ConnectionString);
                    FoundValidConnection = true;
                }
            }
            else
            {
                //nothing to do.  there are no connection strings in the ConfigurationManager
            }
        }
}

Lastly, when I step through debugging on the website, I can see that the only connection string located in ConfigurationManager.ConnectionStrings is 1 with a name of LocalSqlServer and a connection string set to:

data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true

Which seems to be my local testing SQLExpress instance but is not used anywhere in the web app. All references or connection strings to the local testing database in SQLExpress have been removed so I am confused as to how this is showing up and the one in appsettings.json is being ignored. I also don't understand how optionsBuilder.IsConfigured is returning FALSE on the web app. I expected that context to already be configured.

turc1656
  • 105
  • 2
  • 6
  • *I expected that context to already be configured* - it's never configured when you use parameterless constructor (`= new DBEntities();`). It will be configured when you use DI or have a constructor with `DbContextOptions` parameter which is configure externally. – Ivan Stoev Nov 01 '20 at 15:44
  • Ok, thanks for that info. That helps a bit, but I'm not sure how that relates to the error that the web app throws which seems to be related to a totally different database that it shouldn't even be able to know exists. I'm not sure why the web app's ConfigurationManager.ConnectionStrings collection only contains that one that I didn't even set in appsettings.json. I have no idea where it's coming from and why it's trying to use that. – turc1656 Nov 01 '20 at 17:33
  • This has nothing to do with EF Core and is something you have to figure out. To start with, what is `ConfigurationManager` variable, where it's coming from and how it's initialized. – Ivan Stoev Nov 01 '20 at 17:39
  • You seem to be using the `ConfigurationManager` from `System.Configuration` which works with only with `app.config` files. For .Net Core apps (`appsettings.json`) you need a different approach. See https://stackoverflow.com/questions/39083372/how-to-read-connection-string-in-net-core – Ivan Stoev Nov 01 '20 at 17:52
  • 1
    Thank you for pointing me in the right direction. I will follow up and submit an answer to share with others when I figure it out. Seems like dependency injection might be the way to go. – turc1656 Nov 03 '20 at 04:13
  • I ended up skipping dependency injection and instead just added a simple line of code to use ConfigurationBuilder to read the appsettings.json file for the connection string in the web app solution if the ConfigurationManager doesn't produce any valid strings. – turc1656 Nov 10 '20 at 18:31

1 Answers1

0

I ended up changing the last if statement to the following:

                if (FoundValidConnection == false)
                {
                    try
                    {
                        //read from appsettings.json directly instead
                        string connString = new ConfigurationBuilder().SetBasePath(System.IO.Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build().GetSection("ConnectionStrings").GetSection("DefaultConnection").Value;
                        optionsBuilder.UseSqlServer(connString);
                        FoundValidConnection = true;
                    }
                    catch (System.Exception ex)
                    {

                    }
                }

As Ivan noted in the comments above, ConfigurationManager from System.Configuration only works with app.config, which is XML based. ASP.NET Core utilizes appsettings.json which is, obviously, JSON based. So the solution was to modify the last check to be such that if the app.config checks fail and produce nothing then we assume the code is being called by something using appsettings.json and that one line uses ConfigurationBuilder to get the app's directory, build the configuration based on that file and get the expected connection by it's section and name.

References to Microsoft.Extensions.Configuration.FileExtensions and Microsoft.Extensions.Configuration.Json were required for SetBasePath() and AddJsonFile(), respectively.

The try/catch block is needed in the event there is some unforeseen error and the appsettings/json doesn't contain the expected section/name. Although, an error would eventually get thrown up somewhere when trying to interact with the database if nothing was set.

turc1656
  • 105
  • 2
  • 6