0

I have a Razor Pages website application and I'd like to move my database code to a separate project/assembly, which is referenced from my main project/assembly.

Creating a new class project and moving the files from my Data and Model folders is straight forward. But I'm not clear on where my connection string goes (currently, it's in the appsettings.json for my main project).

How do I specify the connection string for my class library?

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • what version of .net are you using? – Gabriel Martinez Bustos Jun 11 '21 at 18:26
  • @GabrielMartinezBustos: 5.0. Please see my question tags. – Jonathan Wood Jun 11 '21 at 18:31
  • did you want the referenced assembly to run in the same process as your website? Or would it need to run as an independent data service? – Matt E. Jun 11 '21 at 18:32
  • @MattE.: I want it to run in the same process as my website, just like it did in a single project. The only difference is that I can write a new application that can reference the new library assembly and access those entities. – Jonathan Wood Jun 11 '21 at 18:36
  • You might think that the class library uses the appsettings.json file that's contained in it's own project but it doesn't. It uses the appsettings.json file of the project that is referencing it. So keep it in main project and do not worry about it. If you plan to use the same class library to some other web project, add those appsettings in it’s main project. – Akshay G Jun 11 '21 at 18:58
  • @AkshayGaonkar: I understand all that. But for adding migrations and updating the database, I am not running any application. – Jonathan Wood Jun 11 '21 at 20:27

1 Answers1

1

The connection string should be configured in the same project where the services connect to the DbContext, so you can leave the appsettings.json as-is.

It is the configuring project (the one that is setting up all the dependencies) that sets the connection to the DB, and the EF migration tool needs the connection to track and apply changes. Any assembly can be used to store migrations, but the EF tool needs to be invoked for a project with an actual connection.

EF Core uses Dependency Injection to configure services for runtime which includes setting the connection string for any DB. The control of how the DB classes interact with their environment is given to the app that is running it, which allows for the same DB code to be used across multiple instances, states and even providers. EF Core uses the current state & provider of the DB where it is deployed to determine changes and scaffolding, so it can only create migrations with a configured instance of a DbContext connected to a DB (usually the development instance).

When a solution is comprised multiple dependent services with a common data model that will need the same migrations, only one of the projects in the solution should be responsible for managing DB state. Whichever project is chosen, it needs to create a DBContext with a valid connection when it starts up for the EF tools to work. ASP.NET Core uses the Microsoft.Extensions.Configuration package and the UseStartup method to make the configuration simple. This can be your existing UI project that would read the connection string from existing settings and pass it to your CustomDbContext at Startup. The EF Tools CLI will use the default profile (the Green run arrow) to get the settings from launchSettings.json and appSettings.json to connect to the DB. The configuration should be in place in each environment you deploy to so that the migrations can be run from the same configuring project and applied as needed.

You can avoid ASP.NET and create a custom startup class in your data package that runs to apply migrations, but this is a lot of extra work for something already in the box. The DbContext class does have an OnConfiguring method that can be used to set the connection. Placing configuration inside of code limits where the software can run, so it should still be set externally.

The DbContext can be referenced from another package:

The DbContext and Models can be defined in a separate 'pure' project, then referenced in the Startup like so:

    using mysolution.Data.CustomDbContext;
...
     services.AddDbContext<CustomDbContext>(options =>
                    options.UseSqlServer(
                        Configuration.GetConnectionString("defaultConnection")));

Configure the DbContext to run and where to store migrations:

The tricky part is applying migrations if you use them. If your Razor assembly was named 'mysolution_UI' and your data project is 'mysolution_Data'. Move the Data, Models, Migrations to mysolution_Data and add this to the startup in mysolution_UI project:

    services.AddDbContext<CustomDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("defaultConnection")
            , b => b.MigrationsAssembly("mysolution_Data")));

Then when adding or deploying the package, the migrations should be invoked from the 'mysolution_UI' project.

   Add-Migration NewMigration -Project mysolution_UI

This allows multiple projects to use the data package for the entities connected to the same DB, but only one is responsible for maintaining the migrations.

Matt E.
  • 950
  • 1
  • 6
  • 15
  • I would want migrations in my class library. That's why I'm not sure how my class library knows how to find the settings in my main app when doing migrations and updating the database. – Jonathan Wood Jun 11 '21 at 19:05
  • @JonathanWood how do you run migrations? – Guru Stron Jun 11 '21 at 19:38
  • @JonathanWood you would still need to set the connection string in the same project as Startup class. The migrations can be stored in the data package, but need to be applied from the configuring package. This is because it doesn't know what DB it is on until it is run by the invoking project. – Matt E. Jun 11 '21 at 19:53
  • @Guru: `add-migration` – Jonathan Wood Jun 11 '21 at 20:26
  • @JonathanWood have you tried to do that after moving context and migrations to separate assembly? :) – Guru Stron Jun 11 '21 at 20:33
  • @MattE.: I'm not sure what the "configuring package" refers to, but can you explain how running commands in the Package Manager Console on the data classes project get the information from the main application? I'm not running the main application. How does it know the connection string, etc.? – Jonathan Wood Jun 12 '21 at 19:37
  • @JonathanWood edited for more context-- no pun intended. The 'configuring package' is the project that creates a DbContext at runtime, EF needs this to create migrations. Since the UI is deployed with this dependency, it can take responsibility to ensure it is done. This doc may help: https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/projects?tabs=vs – Matt E. Jun 14 '21 at 16:23
  • @MattE.: Thanks. I had trouble with some of your explanations. But, for me, if I move my entire *Data* folder to the class library, set the Default project in Package Manager Console to my class library, and also called `SqlServerDbContextOptionsBuilder.MigrationsAssembly()` in my main application's startup with the name of my class library, it seems to work. I'm pretty certain that last step is not required. You seem to be explaining additional steps that I had trouble with. I don't know if I'm missing anything. But I definitely do not understand how it knows what the connection string is. – Jonathan Wood Jun 15 '21 at 20:36