Problem with HealthChecks.UI.SqlServer.Storage migrations - c#

I'm trying to use Healthchecks UI in my asp.net core application with SqlServer.Storage for history purposes. It works with InMemoryStorage (without history part, of course).
So, Startup.cs code is like this:
services.AddHealthChecks()
.AddHangfire(...)
.AddDbContextCheck<...>("Database")
.AddAzureBlobStorage(...)
.AddProcessAllocatedMemoryHealthCheck(...)
.AddCheck(...);
services
.AddHealthChecksUI(settings =>
{
settings.SetEvaluationTimeInSeconds(...);
settings.SetMinimumSecondsBetweenFailureNotifications(...);
settings.MaximumHistoryEntriesPerEndpoint(...);
})
.AddSqlServerStorage(Configuration.GetConnectionString("..."));
later, additional configuration is in Configure method
Everything works when AddInMemoryStorage is used instead of AddSqlServerStorage. When AddSqlServerStorage is used, app crashes on startup, with
SqlException: Invalid object name 'Configurations'.
Sure, SQL tables are missing, but I cannot force [migration from nuget package to be applied to database.
Of course, I could copy/paste migration or create tables in database but I would like to skip that because of future changes and keeping code clean.
Can someone point me in right direction to solve this?
Thanks

I've managed to solve it.
Since migrations weren't applied to database, I ran them in Main like this.
var app = CreateHostBuilder(args).Build();
//run heaalthchecksui migrations
using (var scope = app.Services.CreateScope())
{
var healthChecksDb = scope.ServiceProvider.GetRequiredService<HealthChecksDb>();
healthChecksDb.Database.Migrate();
}
app.Run();
So, maybe that workouround could help someone with similar problem.

In Configure:
using IServiceScope scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope();
using HealthChecksDb healthChecksDb = scope.ServiceProvider.GetRequiredService<HealthChecksDb>();
healthChecksDb.Database.Migrate();
In ConfigureServices:
// build an intermediate service provider
ServiceProvider serviceProvider = services.BuildServiceProvider();
try
{
using HealthChecksDb healthChecksDb = serviceProvider.GetRequiredService<HealthChecksDb>();
healthChecksDb.Database.Migrate();
}
finally
{
serviceProvider.Dispose();
}

Related

Trying to setup .NET 6 with Cosmos DB EF but issues with setup

I'm trying to setup a simple example for using Cosmos DB, .NET 6 using EF. In the UI project startup, I am implementing the db factory as such:
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
// Call CosmosDB connectionString
var cosmosConnectionString = config["cosmosService:CosmosConnectionString"];
var cosmosDatabase = config["cosmosService:DatabaseName"];
services.AddDbContextFactory<DbContext>(optionsBuilder =>
optionsBuilder
.UseCosmos(
connectionString: cosmosConnectionString,
databaseName: cosmosDatabase,
cosmosOptionsAction: options =>
{
options.ConnectionMode(Microsoft.Azure.Cosmos.ConnectionMode.Direct);
options.MaxRequestsPerTcpConnection(20);
options.MaxTcpConnectionsPerEndpoint(32);
}));
I am trying to figure out where to put the call for "CreateDatabaseIfNotExistsAsync" but not finding anyway to adding it. It appears I cannot use the AddDbContextfactory, only implement CosmosClient and add it there. I would prefer to use context factory but so far not finding anything that work.
Any suggestions on implementing it that way, that would be great.
Just an FYI, I have a service project, I've seen examples implementing a provider but again, it uses the CosmosClient as well.
If there is a way to verify and create database beside this, let me know.
Any help would be great.
Thanks!

Injecting Connection String into DbContext Class using Autofac in a TopShelf Service (.Net Framework)

I'm creating a service that needs to reference legacy DLLs for our ERP system so unfortunately .NET framework is how I need to do this. I tried using a Core Worker Service but my calls to the DLLs won't work.
I like using TopShelf and looked up DI to use with this and Autofac seems to be the way to go and it has TopShelf support. I don't like keeping any settings in our config files so that it's easier to deploy to other environments. We have a whole system that keeps settings tied to the environment you are in (dev, test, prod, etc...). In Core apps I simply inject the connection string in the startup and all is well. In this app I want to inject the connection string when I start the service and be able to spin up a DbContect class at anytime and have it use that connection string.
Since I scaffold my data layer and didn't want to modify the generated DbContext, so I created another partial class (MyContext.Custom.cs) with a constructor that allows me to pass in the connection string
public MyContext(string name) : base(name)
{ }
In my Program.cs I'm adding in an Autofac container to TopShelf
HostFactory.Run(serviceConfig =>
{
serviceConfig.UseAutofacContainer(container);
// etc...
}
The container is being built in a function, I tried following many examples but I can't seem to get my constructor with the parameter to be the one that is used. Here are a few of the ways I've tried. Each method produces no error but it runs my default constructor where it is looking for the named connection string in the config file.
static IContainer BuildContainer()
{
string myConnectionString = AppConfig.Instance.Settings["MyConnectionString"];
var builder = new ContainerBuilder();
//builder.RegisterType<MyContext>()
// .UsingConstructor(typeof(string))
// .WithParameter("name", myConnectionString)
// .InstancePerLifetimeScope();
//builder.Register(c => new MyContext(myConnectionString)).As<MyContext>();
//.WithParameter("name", myConnectionString);
//builder.RegisterType<MyContext>()
// .WithParameter("name", myConnectionString)
// .AsSelf();
//.InstancePerLifetimeScope();
builder.Register(c => new MyContext(myConnectionString)).AsSelf();
builder.RegisterType<MainService>();
return builder.Build();
}
I've tried each variation with .As and .AsSelf(). I've put in the .InstancePerLifetimeScope() and also left it out. I'm not really sure what to set the scope to in this case but figured it should work with lifetime. No matter what I've tried I can't seem to get it to use my constructor.
If I've left out any information feel free to ask and I can fill it in. Hopefully someone has done this. I guess I could pass in the connection string everytime I instantiate a DbContext but I was hoping to get it to work like a Core app which is so much nicer.
thanks
EDIT:
Adding code to show how I'm using my DbContext
public bool Start()
{
using(var db = new MyContext())
{
var warehouse = (from w in db.Warehouses
where w.WarehouseCode == "ABCD"
select w).FirstOrDefault();
}
// other code...
}

Parallel EF Core queries with DbContext injection in ASP.NET Core

I have writing an ASP.NET Core web application that needs all the data from some tables of my database to later organize it into readable format for some analysis.
My problem is that this data is potentially massive, and so in order to increase performance i decided to get this data in parallel and not one table at a time.
My issue is that i dont quite understand how to achieve this with the inherit dependency injection as in order to be able to do parallel work, i need to instantiate the DbContext for each of these parallel work.
The below code produces this exception:
---> (Inner Exception #6) System.ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'MyDbContext'.
at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
at Microsoft.EntityFrameworkCore.DbContext.get_ChangeTracker()
ASP.NET Core project:
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDistributedMemoryCache();
services.AddDbContext<AmsdbaContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("ConnectionString"))
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
services.AddSession(options =>
{
options.Cookie.HttpOnly = true;
});
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
if (HostingEnvironment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
loggerFactory.AddLog4Net();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseSession();
app.UseMvc();
}
Controller's action method:
[HttpPost("[controller]/[action]")]
public ActionResult GenerateAllData()
{
List<CardData> cardsData;
using (var scope = _serviceScopeFactory.CreateScope())
using (var dataFetcher = new DataFetcher(scope))
{
cardsData = dataFetcher.GetAllData(); // Calling the method that invokes the method 'InitializeData' from below code
}
return something...;
}
.NET Core Library project:
DataFetcher's InitializeData - to get all table records according to some irrelevant parameters:
private void InitializeData()
{
var tbl1task = GetTbl1FromDatabaseTask();
var tbl2task = GetTbl2FromDatabaseTask();
var tbl3task = GetTbl3FromDatabaseTask();
var tasks = new List<Task>
{
tbl1task,
tbl2task,
tbl3task,
};
Task.WaitAll(tasks.ToArray());
Tbl1 = tbl1task.Result;
Tbl2 = tbl2task.Result;
Tbl3 = tbl3task.Result;
}
DataFetcher's sample task:
private async Task<List<SomeData>> GetTbl1FromDatabaseTask()
{
using (var amsdbaContext = _serviceScope.ServiceProvider.GetRequiredService<AmsdbaContext>())
{
amsdbaContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
return await amsdbaContext.StagingRule.Where(x => x.SectionId == _sectionId).ToListAsync();
}
}
I'm not sure you do actually need multiple contexts here. You have have noticed that in the EF Core docs, there's this conspicuous warning:
Warning
EF Core does not support multiple parallel operations being run on the same context instance. You should always wait for an operation to complete before beginning the next operation. This is typically done by using the await keyword on each asynchronous operation.
This is not entirely accurate, or rather, it's simply worded somewhat confusingly. You can actually run parallel queries on a single context instance. The issue comes in with EF's change tracking and object fixup. These types of things don't support multiple operations happening at the same time, as they need to have a stable state to work from when doing their work. However, that really just limits your ability to do certain things. For example, if you were to run parallel saves/select queries, the results could be garbled. You might not get back things that are actually there now or change tracking could get messed up while it's attempt to create the necessary insert/update statements, etc. However, if you're doing non-atomic queries, such as selects on independent tables as you wish to do here, there's no real issue, especially, if you're not planning on doing further operations like edits on the entities you're selecting out, and just planning on returning them to a view or something.
If you truly determine you need separate contexts, your best bet is new up your context with a using. I haven't actually tried this before, but you should be able to inject DbContextOptions<AmsdbaContext> into your class where these operations are happening. It should already be registered in the service collection since it's injected into your context when the service collection instantiates that. If not, you can always just build a new one:
var options = new DbContextOptionsBuilder()
.UseSqlServer(connectionString)
.Build()
.Options;
In either case, then:
List<Tbl1> tbl1data;
List<Tbl2> tbl2data;
List<Tbl3> tbl3data;
using (var tbl1Context = new AmsdbaContext(options))
using (var tbl2Context = new AmsdbaContext(options))
using (var tbl3Context = new AmsdbaContext(options))
{
var tbl1task = tbl1Context.Tbl1.ToListAsync();
var tbl2task = tbl2Context.Tbl2.ToListAsync();
var tbl3task = tbl3Context.Tbl3.ToListAsync();
tbl1data = await tbl1task;
tbl2data = await tbl2task;
tbl3data = await tbl3task;
}
It's better to use await to get the actual result. This way, you don't even need WaitAll/WhenAll/etc. and you're not blocking on the call to Result. Since tasks return hot, or already started, simply postponing calling await until each has been created is enough to buy you parallel processing.
Just be careful with this that you select everything you need within the usings. Now that EF Core supports lazy-loading, if you're using that, an attempt to access a reference or collection property that hasn't been loaded will trigger an ObjectDisposedException, since the context will be gone.
Simple answer is - you do not. You need an alternative way to generate dbcontext instances. The standard approach is to get the same instance on all requests for a DbContext in the same HttpRequest. You can possibly override ServiceLifetime, but that then changes the behavior of ALL requests.
You can register a second DbContext (subclass, interface) with a different service lifetime. Even then you need to handle the creation manually as you need to call it once for every thread.
You manaully create them.
Standard DI simply comes to an end here. It is QUITE lacking, even compared to older MS DI frameworks where you possibly could put up a separate processing class with an attribute to override creation.

How to make Entity Framework create database before using it outside DbContext?

In certain test environments, I have configured my application to create a new instance of it's database using Entity Framework migrations when the database does not already exist.
I am also using Hangfire, and have configured it use SQL Server in my OWIN startup class. At present I am instantiating a new instance of my DbContext to force database creation prior to configuring Hangfire to use the database:
public class Startup
{
public void Configuration(IAppBuilder app)
{
using (var dbContext = new MyDbContext())
{
// Force database creation before configuring Hangfire to use it.
}
GlobalConfiguration.Configuration.UseSqlServerStorage("myConnection");
// ...
}
}
This feels a bit hacky. What is the recommended way to ensure Entity Framework has created the database before it is used outside of the DbContext?
Turns out that the snippet in the question isn't necessarily enough by itself (depending upon platform and other EF configuration settings), so we actually make it explicit in the code now (as per Diana's comment), which also makes it feel less hacky:
// Trigger database creation before Hangfire tries to use it.
var initializer = new MigrateDatabaseToLatestVersion<MyDbContext, MyConfiguration>(true);
Database.SetInitializer(initializer);
var connectionString = this.Configuration.GetConnectionString("MyConnectionString");
using (var dbContext = new MyDbContext(connectionString))
{
dbContext.Database.Initialize(false);
}

Add migration with different assembly

I am working on a project with ASP.NET CORE 1.0.0 and I am using EntityFrameworkCore. I have separate assemblies and my project structure looks like this:
ProjectSolution
-src
-1 Domain
-Project.Data
-2 Api
-Project.Api
In my Project.Api is the Startup class
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ProjectDbContext>();
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ProjectDbContext>()
.AddDefaultTokenProviders();
}
The DbContext is in my Project.Data project
public class ProjectDbContext : IdentityDbContext<IdentityUser>
{
public ProjectDbContext(DbContextOptions<ProjectDbContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var builder = new ConfigurationBuilder();
builder.SetBasePath(Directory.GetCurrentDirectory());
builder.AddJsonFile("appsettings.json");
IConfiguration Configuration = builder.Build();
optionsBuilder.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"));
base.OnConfiguring(optionsBuilder);
}
}
When I try to make the initial migration, I get this error:
"Your target project 'Project.Api' doesn't match your migrations assembly 'Project.Data'. Either change your target project or change your migrations assembly.
Change your migrations assembly by using DbContextOptionsBuilder. E.g. options.UseSqlServer(connection, b => b.MigrationsAssembly("Project.Api")). By default, the migrations assembly is the assembly containing the DbContext.
Change your target project to the migrations project by using the Package Manager Console's Default project drop-down list, or by executing "dotnet ef" from the directory containing the migrations project."
After I seeing this error, I tried to execute this command located in Project.Api:
dotnet ef --startup-project ../Project.Api --assembly "../../1 Data/Project.Data" migrations add Initial
and I got this error:
"Unexpected value '../../1 Domain/Project.Data' for option 'assembly'"
I don't know why I get this error, when I try to execute the command with the '-assembly' parameter.
I can't create a Initial Migration from other assembly and I've searched for information about it but didn't got any results.
Has someone had similar issues?
All EF commands have this check:
if (targetAssembly != migrationsAssembly)
throw MigrationsAssemblyMismatchError;
targetAssembly = the target project you are operating on. On the command line, it is the project in the current working directory. In Package Manager Console, it is whatever project is selected in the drop down box on the top right of that window pane.
migrationsAssembly = assembly containing code for migrations. This is configurable. By default, this will be the assembly containing the DbContext, in your case, Project.Data.dll.
As the error message suggests, you have have a two options to resolve this
1 - Change target assembly.
cd Project.Data/
dotnet ef --startup-project ../Project.Api/ migrations add Initial
// code doesn't use .MigrationsAssembly...just rely on the default
options.UseSqlServer(connection)
2 - Change the migrations assembly.
cd Project.Api/
dotnet ef migrations add Initial
// change the default migrations assembly
options.UseSqlServer(connection, b => b.MigrationsAssembly("Project.Api"))
I had the same problem until I noticed that on the package manager console top bar => "Default Projects" was supposed to be "Project.Data" and not "Project.API".
Once you target the "Project.Data" from the dropdown list and run the migration you should be fine.
Using EF Core 2, you can easily separate your Web project from your Data (DbContext) project. In fact, you just need to implement the IDesignTimeDbContextFactory interface. According to Microsoft docs, IDesignTimeDbContextFactory is:
A factory for creating derived DbContext instances. Implement this
interface to enable design-time services for context types that do not
have a public default constructor. At design-time, derived DbContext
instances can be created in order to enable specific design-time
experiences such as Migrations. Design-time services will
automatically discover implementations of this interface that are in
the startup assembly or the same assembly as the derived context.
In the bottom code snippet you can see my implementation of DbContextFactory which is defined inside my Data project:
public class DbContextFactory : IDesignTimeDbContextFactory<KuchidDbContext>
{
public KuchidDbContext CreateDbContext(string[] args)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var dbContextBuilder = new DbContextOptionsBuilder<KuchidDbContext>();
var connectionString = configuration.GetConnectionString("Kuchid");
dbContextBuilder.UseSqlServer(connectionString);
return new KuchidDbContext(dbContextBuilder.Options);
}
}
Now, I can initialize EF migration by setting my Web project as the StartUp project and selecting my Data project inside the Package Manager Console.
Add-Migration initial
You can find more details here. However, this blog post uses an obsoleted class instead of IDesignTimeDbContextFactory.
Add Migration With CLI Command:
dotnet ef migrations add NewMigration --project YourAssemblyName
Add Migration With PMC Command:
Add-Migration NewMigration -Project YourAssemblyName
Link About CLI Commands
Link About PMC Commands
I ran on the same problem and found this
We’re you trying to run your migrations on a class library? So was I. Turns out this isn’t supported yet, so we’ll need to work around it.
EDIT: I found solution on this git repo
Currently I think EF only supports to add migrations on projects not yet on class libraries.
And just side note for anybody else who wants to add migrations to specific folder inside your project:
EF CLI not support this yet. I tried --data-dir but it didn't work.
The only thing works is to use Package Manager Console:
Pick your default project
use -OutputDir command parameter, .e.g., Add-Migration InitConfigurationStore -OutputDir PersistedStores/ConfigurationStore command will output the mgiration to the folder 'PersistedStores/ConfigurationStore' in my project.
Updates as of 10/12/2017
public void ConfigureServices(IServiceCollection services)
{
...
string dbConnectionString = services.GetConnectionString("YOUR_PROJECT_CONNECTION");
string assemblyName = typeof(ProjectDbContext).Namespace;
services.AddDbContext<ProjectDbContext>(options =>
options.UseSqlServer(dbConnectionString,
optionsBuilder =>
optionsBuilder.MigrationsAssembly(assemblyName)
)
);
...
}
Updates as of 1/4/2021
I am using EF Core 5.0 this time. I was hoping optionBuilder.MigrationAssembly() method would work when you want to generate migrations under a folder in the target project but it didn't.
The structure I have this time is:
src
- presentation
- WebUI
- boundedContext
- domain
- application
- infrastructure
- data/
- appDbContext
- email-services
- sms-services
See I have the infrastructure as a class library, and it contains multiple folders because I want to just have a single project to contain all infrastructure related services. Yet I would like to use folders to organize them.
string assemblyName = typeof(ProjectDbContext).Namespace would give me the correct path "src/infrastructure/data", but doing add-migration still fails because that folder is not an assembly!
Could not load file or assembly. The system cannot find the file
specified.
So the only thing that actually works is, again, to specify the output folder...
Using .NET Core CLI you would have to open the command line under your target project, and do the following:
dotnet ef migrations add Init
-o Data\Migrations
-s RELATIVE_PATH_TO_STARTUP_PROJECT
Directory Structure
Root
APIProject
InfrastructureProject
By going Root directory
To add migration
dotnet ef migrations add Init --project InfrastructureProject -s APIProject
To update database
dotnet ef database update --project InfrastructureProject -s APIProject
(ASP.NET Core 2+)
Had the same issue.
Here is what I did:
Reference the project that contains the DbContext (Project.A) from the project that will contain the migrations (Project.B).
Move the existing migrations from Project.A to Project.B
(If you don't have migrations - create them first)
Configure the migrations assembly inside Project.A
options.UseSqlServer(
connectionString,
x => x.MigrationsAssembly("Project.B"));
Assuming your projects reside in the same parent folder:
dotnet ef migrations add Init --p Project.B -c DbContext
The migrations now go to Project.B
Source: Microsoft
dotnet ef update-database --startup-project Web --project Data
Web is my startup project
Data is my the my class library
There are multiple projects included in the Solution.
Solution
|- MyApp (Startup Proj)
|- MyApp.Migrations (ClassLibrary)
Add-Migration NewMigration -Project MyApp.Migrations
Note: MyApp.Migrations also includes the DbContext.
The below command did the trick for me. I'm using VS Code and I run the following command:
SocialApp.Models> dotnet ef migrations add InitialMigartion --startup-project ../SocialApp.API
Courtesy: https://github.com/bricelam/Sample-SplitMigrations
This is for EF Core 3.x.
Based on this answer from Ehsan Mirsaeedi and this comment from Ales Potocnik Hahonina, I managed to make Add-Migration work too.
I use Identity Server 4 as a NuGet package and it has two DB contexts in the package.
Here is the code for the class that implements the IDesignTimeDbContextFactory interface:
public class PersistedGrantDbContextFactory : IDesignTimeDbContextFactory<PersistedGrantDbContext>
{
public PersistedGrantDbContext CreateDbContext(string[] args)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var dbContextBuilder = new DbContextOptionsBuilder<PersistedGrantDbContext>();
var connectionString = configuration.GetConnectionString("db");
dbContextBuilder.UseSqlServer(connectionString, b => b.MigrationsAssembly("DataSeeder"));
return new PersistedGrantDbContext(dbContextBuilder.Options, new OperationalStoreOptions() { ConfigureDbContext = b => b.UseSqlServer(connectionString) });
}
}
Compared to the answer of Ehsan Mirsaeedi I modified these:
I added the MigrationsAssembly:
dbContextBuilder.UseSqlServer(connectionString, b => b.MigrationsAssembly("DataSeeder"));
Where the "DataSeeder" is the name of my startup project for seeding and for migrations.
I added an options object with ConfigureDbContext property set to the connection string:
return new PersistedGrantDbContext(dbContextBuilder.Options, new OperationalStoreOptions() { ConfigureDbContext = b => b.UseSqlServer(connectionString) });
It is now usable like this:
'Add-Migration -Context PersistedGrantDbContext
At this point, when a migration has been created, one can create a service for this in a migration project having a method like this:
public async Task DoFullMigrationAsync()
{
using (var scope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var persistedGrantDbContextFactory = new PersistedGrantDbContextFactory();
PersistedGrantDbContext persistedGrantDbContext = persistedGrantDbContextFactory.CreateDbContext(null);
await persistedGrantDbContext.Database.MigrateAsync();
// Additional migrations
...
}
}
I hope I helped someone.
Cheers,
Tom
All you have to do, is modify your ConfigureServices like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ProjectDbContext>(item => item.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly("Project.Api")));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ProjectDbContext>()
.AddDefaultTokenProviders();
}
By Default VS will use the Assembly of the project where the DbContext is stored. The above change, just tells VS to use the assembly of your API project.
You will still need to set your API project as the default startup project, by right clicking it in the solution explorer and selecting Set as Startup Project
Mine is a single .net core web project.
Had to ensure 1 thing to resolve this error. The following class must be present in the project.
public class SqlServerContextFactory : IDesignTimeDbContextFactory<SqlServerContext>
{
public SqlServerContext CreateDbContext(string[] args)
{
var currentEnv = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var configuration = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{ currentEnv ?? "Production"}.json", optional: true)
.Build();
var connectionString = configuration.GetConnectionString("MsSqlServerDb");
var optionsBuilder = new DbContextOptionsBuilder<SqlServerContext>();
//var migrationAssembly = typeof(SqlServerContext).Assembly.FullName;
var migrationAssembly = this.GetType().Assembly.FullName;
if (connectionString == null)
throw new InvalidOperationException("Set the EF_CONNECTIONSTRING environment variable to a valid SQL Server connection string. E.g. SET EF_CONNECTIONSTRING=Server=localhost;Database=Elsa;User=sa;Password=Secret_password123!;");
optionsBuilder.UseSqlServer(
connectionString,
x => x.MigrationsAssembly(migrationAssembly)
);
return new SqlServerContext(optionsBuilder.Options);
}
}
Note there the migration assembly name.
//var migrationAssembly = typeof(SqlServerContext).Assembly.FullName;
I have commented that out. That is the culprit in my case. What is needed is the following.
var migrationAssembly = this.GetType().Assembly.FullName;
With that in place the following two commands worked perfectly well.
Add-Migration -StartupProject MxWork.Elsa.WebSqLite -Context "SqlServerContext" InitialMigration
Add-Migration InitialMigration -o SqlServerMigrations -Context SqlServerContext
If you want a reference of such a project, take a look at this git hub link
There you should find a project attached with the name Elsa.Guides.Dashboard.WebApp50.zip. Download that see that web app.
I was facing similar issue, though answers seems straight forward somehow they didn't work.
My Answer is similar to #Ehsan Mirsaeedi, with small change in DbContextFactory class. Instead of Adding migration assembly name in Startup class of API, I have mentioned in DbContextFactory class which is part of Data project(class library).
public class DbContextFactory : IDesignTimeDbContextFactory<KuchidDbContext>
{
public KuchidDbContext CreateDbContext(string[] args)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var dbContextBuilder = new DbContextOptionsBuilder<KuchidDbContext>();
var connectionString = configuration.GetConnectionString("connectionString");
var migrationAssemblyName= configuration.GetConnectionString("migrationAssemblyName");
dbContextBuilder.UseSqlServer(connectionString, o => o.MigrationAssembly(migrationAssemblyName));
return new KuchidDbContext(dbContextBuilder.Options);
}
}
You would need 'Microsoft.Extensions.Configuration' and 'Microsoft.Extensions.Configuration.Json' for SetBasePath & AddJsonFile extensions to work.
Note: I feel this is just a work around. It should pickup the DbContextOptions from the startup class somehow it is not. I guess there is definitely some wiring issue.
If you have solution with few projects, where
API - startup here
EF - db context here
then to perform migration:
install Microsoft.EntityFrameworkCore.Tools for API
open Package Manager Console in Visual Studio
perform Add-Migration InitialCreate
notice that "DefaultProject: EF" should be selected in the console.
I have resolved it by adding below line in Startup.cs. Hope it will help you also. I have used Postgres you can use Sql Server instead of that
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddSigningCredential(cert)
.AddCustomUserStore<IdentityServerConfigurationDbContext>()
// this adds the config data from DB (clients, resources)
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseNpgsql(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
})
// this adds the operational data from DB (codes, tokens, consents)
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseNpgsql(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
// this enables automatic token cleanup. this is optional.
options.EnableTokenCleanup = true;
options.TokenCleanupInterval = 30;
});
temporary rename docker proj file, solve on my issue
For all of you who have multiple startup projects.
Notice that you need to set your target project as startup project - Project.Api(form the question example) should be the startup project.
Hope that will help someone :)

Categories