ASP.NET: Check if run from migration - c#

I have some code in my ConfigureServices that fails when running a migration:
dotnet ef migrations list
I'm trying to add a Certificate but it can't find the file (it works when starting the project as a whole). So is there a way to do something like this:
if (!CurrentEnvironment.IsMigration()) {
doMyStuffThatFailsInMigration()
}
That way I could keep my code as it is but just execute it when not running it in a migration.
Thanks

Just set a static flag in the Main method (which is not called by the dotnet-ef tool):
public class Program
{
public static bool IsStartedWithMain { get; private set; }
public static void Main(string[] args)
{
IsStartedWithMain = true;
...
}
}
and then check it when needed:
internal static void ConfigureServices(WebHostBuilderContext context, IServiceCollection services)
{
if (Program.IsStartedWithMain)
{
// do stuff which must not be run upon using the dotnet-ef tool
}
}
EDIT: in Dotnet 6.0 there's no separate ConfigureServices method. Everything is initialized in the Main method (can be created with dotnet new .. --use-program-main). In this case a flag can be used for skipping EF stuff:
private static bool IsStartedWithMain =>
Assembly.GetEntryAssembly() == Assembly.GetExecutingAssembly();

My current solution to detecting if a migration has not occurred:
using System.Linq;
// app is of type IApplicationBuilder
// RegisteredDBContext is the DBContext I have dependency injected
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetService<RegisteredDBContext>();
if (context.Database.GetPendingMigrations().Any())
{
var msg = "There are pending migrations application will not start. Make sure migrations are ran.";
throw new InvalidProgramException(msg);
// Instead of error throwing, other code could happen
}
}
This assumes that the migrations have been synced to the database already. If only EnsureDatabase has been called, then this approach does not work, because the migrations are all still pending.
There are other method options on the context.Database. GetMigrations and GetAppliedMigrations.

Related

grab UserSecrets from a NuGet package

I've written a C# class library for my company to use internally, and it uses DotNet UserSecrets to allow each developer to have their own credentials set without needing to worry about accidentally committing them. It worked fine during testing, but after installing it as a NuGet package as opposed to a project dependency, it no longer seems to be able to read from the secrets.json file. I'm wondering if this is a security thing that C# prevents, or if I need to do something else to enable that functionality in an external package.
The package code looks like this:
using Microsoft.Extensions.Configuration;
using TechTalk.Specflow;
namespace Testing.Utilities
{
[Binding]
public class Context
{
private static IConfigurationRoot configuration { get; set; }
private static FeatureContext feature_context;
// SpecFlow attribute runs this before anything else executes
[BeforeFeature(Order = 1)]
private static void SetFeatureContext(FeatureContext context)
{
try
{
configuration = new ConfigurationBuilder()
.AddUserSecrets<Context>()
.Build();
}
catch { }
feature_context = context;
test_context = context.FeatureContainer.Resolve<TestContext>();
}
public static string GetSecretVariable(string name)
{
object v = null;
// if the user secrets were found
if (configuration != null)
{
v = configuration[name];
}
if (v == null)
{
Logger.Warning($"secret variable '{name}' not found");
return null;
}
return v.ToString();
}
}
}
And in the calling code which always gets Null from the getter method:
using Testing.Utilities; // via NuGet package
namespace Testing
{
public static void Main()
{
System.Console.WriteLine($"found {Context.GetSecretVariable("super_secret")}");
}
}
Update:
It works as expected when I drag my locally built .nupkg file into my NuGet package cache and replace the one pulled from the repo. I updated the version number and pushed the change so I know they are on the same version, and it still only worked when I manually inserted my build. Now I'm more confused...
I ported the project from .NET Framework 4.6.1 to .NET 6 and it seemed to fix it. Kinda drastic change, but easy enough refactor and 461 is EOL anyways.

How do I add a DbContext to an MSTest project?

I trying to test some code that uses Entity Framework, but I can't figure out how to reference the EF Context classes from the separate MSTest project. Both projects are in the same solution.
Cannot convert lambda expression to type 'DbContextOptions' because it is not a delegate type
In my Test case:
[TestClass]
public class GreenCardUserTest
{
[TestMethod]
public void TestAddUser()
{
// REFERENCE TO OTHER PROJECT. WORKS FINE
AppUserViewModel a = new AppUserViewModel();
//LIKELY INCORRECT attempt to duplicate code from Startup.cs in other project
using (GreenCardContext _gc = new GreenCardContext(options => options.UseSqlServer(Configuration.GetConnectionString("MyConnection"))))
{
new GCLandingUserModel().AddUser(a,_gc);
}
}
}
Excerpt from main project Startup.cs (which works fine):
services.AddDbContext<GreenCardContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyConnection")));
I would suggest using InMemoryDatabase. In your test class, use [TestInitialize] to setup your dummy database:
[TestClass]
public class GreenCardUserTest
{
private readonly context;
[TestInitialize]
public Setup()
{
DbContextOptions<GreenCardContext> options;
var builder = new DbContextOptionsBuilder<GreenCardContext>();
builder.UseInMemoryDatabase();
var options = builder.Options;
context = new GreenCardContext(options);
}
[TestMethod]
public void TestAddUser()
{
// user context here...
}
}
What you have to do is:
1) Add a reference in your test project to your context's project (if you haven't already)
2) Add references to Entity Framework to your test project
3) Add an appconfig to your test project and set entity framework config on it. Your test will read the configuration from it's own config, not your app's. Very useful as you can, by example, use dblocal and codefirst in tests and sqlserver when on running :)
You have done some of this, I think the point you are missing is the third :)
The code that you have from Startup.cs is using a delegate to tell your application how to build your DbContext at runtime.
However in your test, you need to actually provide an instance of DbContextOptions, not just a delegate. To do this, you can use DbContextOptionsBuilder:
var options = new DbContextOptionsBuilder<GreenCardContext>()
.UseSqlServer(Configuration.GetConnectionString("MyConnection"))
.Options;
using (GreenCardContext _gc = new GreenCardContext(options))
{
new GCLandingUserModel().AddUser(a,_gc);
}
Also, if you do insist on unit testing your DbConext, you may want to look into using InMemoryDatabase so that you don't need an open SQL connection in your tests. See this document for more details.

Entity Framework, Automatic apply Migrations

I am using Entity Framework Code First approach with AutomaticMigrationsEnabled = true:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DbContext, MigrateDBConfiguration>());
//////////////////////////////////
public class MigrateDBConfiguration : System.Data.Entity.Migrations.DbMigrationsConfiguration<DbContext>
{
public MigrateDBConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
}
The first run of the project creates the database and tables as expected. After changing my model by adding or dropping fields, I ran Add-Migration. The Migration class was generated but after running the project this exception occurs:
An exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll but was not handled in user code
Additional information: The model backing the 'DBContext' context has
changed since the database was created.
EDIT: Per the guidance in the answer of arturo menchaca I changed my code like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DBContext, MigrateDBConfiguration<DBContext>>());
...
After the change this exception is occurring:
There is already an object named 'MyTable' in the database.
How can I apply my database migration?
Automatic Migrations means that you don't need to run add-migration command for your changes in the models, but you have to run update-database command manually.
If Automatic Migrations is enabled when you call update-database, if there are pending changes in your models, an 'automatic' migration will be added and database will be updated.
If you want that your database is updated without need to call update-database command, you can add Database.SetInitializer(...) in OnModelCreating() method on your context, like so:
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MigrateDBConfiguration>());
}
...
}
public class MigrateDBConfiguration : System.Data.Entity.Migrations.DbMigrationsConfiguration<MyContext>
{
...
Note that you should declare DbMigrationsConfiguration and MigrateDatabaseToLatestVersion with your real context, not the default DbContext.
Finally, I found a solution to my problem. I call this method in each application start :
public void InitializeDatabase(DataAccessManager context)
{
if (!context.Database.Exists() || !context.Database.CompatibleWithModel(false))
{
var configuration = new DbMigrationsConfiguration();
var migrator = new DbMigrator(configuration);
migrator.Configuration.TargetDatabase = new DbConnectionInfo(context.Database.Connection.ConnectionString, "System.Data.SqlClient");
var migrations = migrator.GetPendingMigrations();
if (migrations.Any())
{
var scriptor = new MigratorScriptingDecorator(migrator);
var script = scriptor.ScriptUpdate(null, migrations.Last());
if (!string.IsNullOrEmpty(script))
{
context.Database.ExecuteSqlCommand(script);
}
}
}
}
If you have change in your entities, you need first run add-migration to create the migration script.
After that in your Global.asax
you need to have some code like this
var configuration = new MyProject.Configuration();
var migrator = new System.Data.Entity.Migrations.DbMigrator(configuration);
migrator.Update();
every time that you run your asp.net project it'll check if you have a new migration to run and run update-database automatically for you.
Microsoft addresses migrations at runtime, here.
For example, you can do this in Program.cs: (tested working in .NET 5.0 preview)
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
MigrateDatabase(host);
host.Run();
}
private static void MigrateDatabase(IHost host)
{
using var scope = host.Services.CreateScope();
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<ApplicationDbContext>();
context.Database.Migrate();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
using Microsoft.EntityFrameworkCore;
await _dbContext.Database.MigrateAsync();
_dbContext.Database.Migrate();
OR
await _dbContext.Database.EnsureCreatedAsync();
_dbContext.Database.EnsureCreated();
both method check if database exist, if not they both create it.
Migrate() uses migrations and is suitable if you use migrations or relational database.
EnsureCreated() does not use migrations which means once db is created using this method no further migrations can be executed over it.

EF 6.1 how to swap migration configurations for testing?

I am trying to use Integration testing using EF 6.1 and run into a problem that my migration configuration settings are used where I dont need them. And I cant figure out how to swap them out for testing.
Here is my Test Class:
[TestClass]
public class SXSeasonConverterTests
{
public void RecreateDatabaseForTesting()
{
Database.SetInitializer(new TestDatabaseSeedingInitializer());
using (var context = new BaseNFLContext("NFLContextIntegrationTests"))
{
context.Database.Initialize(true);
}
}
public SXSeasonConverterTests()
{
RecreateDatabaseForTesting();
}
}
Here is my Initializer class:
public class TestDatabaseSeedingInitializer : DropCreateDatabaseAlways<BaseNFLContext>
{
protected override void Seed(BaseNFLContext context)
{
//Add Teams
context.Teams.Add(new Team { Code = "ARZ", Name = "Arizona Cardinals" });
context.Teams.Add(new Team { Code = "ATL", Name = "Atlanta Falcons" });
...
}
}
However when I try to run the test, I get the error that my AutomaticMigrations are disabled. When I looked further I found that It uses this code on Initialize:
internal sealed class NFLConfiguration : DbMigrationsConfiguration<BaseNFLContext>
{
public NFLConfiguration()
{
AutomaticMigrationsEnabled = false;
AutomaticMigrationDataLossAllowed = false;
}
}
This code is obviously there for production. However when doing testing how can I swap those migration configurations and set AutomaticMigrationsEnabled = true;?
I used to test my EF stuff using a special unittesting database and executed the tests in a TransactionScope which was rolled back at the end of the test. This way, no data was actually stored in the database.
I wasn't fast, but it suited our purpose.
You should create a separate project for testing and have a separate Db context that points to a test database. You can create something like a IDbContext interface that tells you which object models need to be tested. Also, the data access layer needs to allow you to inject this test Db context as a dependency.

EntityFramework.Migrations, using DbMigrator in a C# class

I just installed the new EntityFramework.Migrations package. I scaffoled my migrations following this tutorial: http://blogs.msdn.com/b/adonet/archive/2011/09/21/code-first-migrations-alpha-3-no-magic-walkthrough.aspx
Using the Powershell window, everything works fine.
But we need to create a class that will rollback all the migrations for our automated tests.
So I made a simple class that looks like this:
public class CustomMigrator
{
public void DropDatabase()
{
new DbMigrator(new Settings()).Update("0");
}
public void RegenerateDatabase()
{
new DbMigrator(new Settings()).Update();
}
}
Settings is my DbMigrationContext implementation that looks like this:
public class Settings : DbMigrationContext<MyDb>
{
public Settings()
{
AutomaticMigrationsEnabled = false;
SetCodeGenerator<CSharpMigrationCodeGenerator>();
AddSqlGenerator<SqlConnection, SqlServerMigrationSqlGenerator>();
}
}
When I call this:
new CustomMigrator().DropDatabase();
I get a weird exception:
The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047)
I know that Migrations are still in alpha, but I was wondering if anyone have been able to run the migrations using DbMigrator?
Thanks.
I just found my problem, it is because I was using EntityFrameworkProfiler and there is a bug with the latest EF release that breaks the profiler.
http://blogs.hibernatingrhinos.com/5121/entity-framework-june-2011-ctp-v4-2-is-now-supported-in-entity-framework-profiler
For the moment I did not need the profiler, so I just removed the line of code that was initializing the profiler and now it works.

Categories