I have an Entity Framework Core + ASP.NET Core application and when my application starts up I want to ensure that the database is created, and eventually (once I have migrations) I want to ensure that those are also run.
Initially I put Database.EnsureCreated() into the constructor of my DbContext but that appears to run every time someone hits my application since a new instance of the DbContext is created each time.
I tried to put it into my startup code, but I need an instance of my DbContext to do that and it is unclear how exactly to get one. I am configuring EF as so:
serviceCollection.AddEntityFramework()
.AddSqlServer()
.AddDbContext<Models.MyContext>(options => options.UseSqlServer(...));
I don't see a way to get an instance of the DbContext from the service collection, and I don't see any appropriate singleton to inject a DbContext into so I can do some one-time initialization.
So what is the best place to ensure some code related to my DbContext is called once per application run?
At the time of this writing, there is not a "correct" place to run code on application startup such that it executes within the request scope (see https://github.com/aspnet/Hosting/issues/373).
For now, the workaround is to do the following, but it won't work in more complex multi-application scenarios (see https://github.com/aspnet/EntityFramework/issues/3070#issuecomment-142752126)
public class Startup
{
...
public void Configure(IApplicationBuilder applicationBuilder, ...)
{
...
// NOTE: this must go at the end of Configure
var serviceScopeFactory = applicationBuilder.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
using (var serviceScope = serviceScopeFactory.CreateScope())
{
var dbContext = serviceScope.ServiceProvider.GetService<MyDbContext>();
dbContext.Database.EnsureCreated();
}
}
}
I wonder why you would run to run EnsureCreated as part of your service anyway. Do you really want your webserver to create or update the database schema? Why would the webserver be up and serving request if the database is not up to date?
Do you really trust your migrations so much that they don't ruin data when executed, that you don't want to test the data after running them?
In addition, this will require you to give the webserver database user permissions to change the database schema. This is a vulnerability in itself - someone taking over your webserver will be able to modify your database schema.
I suggest you create the database and apply migrations in a small utility you run yourself, not as part of your web application.
I think zmbq's suggestion is a correct one and there is a way to ensure that migrations are run along with the deployment, so that binaries and database changes are in sync, using Visual Studio's Publish functionality.
When publishing against an IIS instance, one can specify target database connection string to use to also run required migrations:
This will ensure that changes are applied only when needed (not every time application starts) and that application runs using the least required database rights (i.e. database writer, reader etc.) as opposed to rights to alter tables, create indexes etc.
Related
I am curious about EF behaviour and cannot figure out what is going on under the hood.
In Program.cs of a Blazor project, I have the following way to create the connection string:
var connectionString = $"Data Source=tcp:{dbHost},{dbPort};Initial Catalog={dbname};User ID={dbuser};Password={dbPassword};Encrypt=False";
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
In appsettings.json, I have another piece:
"Default": "Server=tcp:localhost,5532;Initial Catalog=.....etc
I am using Docker, environment variables and therefore the Program.cs is necessary, I cannot use the static one from appsettings.json.
Interesting things happen as I install EF, prepare migrations and when I run
dotnet ef database update
It somehow uses the Program.cs variant, which is correct but which lead to nowhere because at that time, my Docker machines are not running (one of them is providing SQL Server database).
Rather I need to use e.g. the static one.
If I do so, for example by adding manual line
connectionString = builder.Configuration.GetConnectionString("Default") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
or if I use parameter
dotnet ef database update --connection "Server=tcp:...
all works fine, the database gets updated, I the comment out the line, let the program run, docker containers run and all works like a charm.
But I do not understand at all HOW EF does this?
This means he has to have some way how to "interpret" or "run" at least Program.cs because I can imagine many way how the connectionString variable can be combined, concatenated, .Format-ted etc...
I am frustrated I do not understand the mechanism. Because you run
dotnet ef database update
at the time, your program is stopped or is NOT running.
Everywhere, all people repeat that it is appsettings.json which is used, but apparently according to my own experience, this is not true, rather some result of program run is used.
But even after program finishes, variable content is not kept, or?
What place or method in EF is used to get the non-trivially built connection string? Can it use C# interpreter to somehow "track" all the variables in Program.cs? This seems over-complicated.
As described above, I have no barrier in my project, I just want to understand the principle.
I have my services configured and available via dependency injection.
What I try to do is to get one at runtime (that's why I'm looking non-standard way). That means - not in a constructor, not hardcoded, I need to get a service dynamically exactly when it's needed.
Why? Entity Framework (Core).
Well, version 3.1 doesn't have any means to reset the data context. Yesterday I found out that if I try to remove entry blocked by foreign key constraint - the context breaks irreversibly. The EF tries to save invalid changes each time, despite the first try failed. Using Database.RollbackTransaction doesn't help. Nothing does.
EF has more ugly "features", like default sandbox mode, where the context sees only its own changes, ignoring all external changes to the database. Like someone just thought "databases are primary single user, right?".
Anyway, no way of refreshing / reloading context in EF Core 3.1, failed write to database irreversibly breaks the context.
The way around this is to recreate the context when it needs to be refreshed / reset. That means I need to dispose the old context and create a new one.
I thought that configuring the data context service as Transient would achieve just that. Well - not exactly. I tried to remove an entry I couldn't remove, it failed and the only way I could do any successful write to the database was to reload the Blazor page.
I think of a solution like this:
DataContext.Database.BeginTransaction();
try {
// some writing to the data context
DataContext.Database.CommitTransaction();
} catch {
DataContext.Database.RollbackTransaction();
DataContext.Dispose();
DataContext = GetService<MyDataContext>();
}
So I wonder how to get the service other then store IApplicationBuilder in a static property.
What's wrong with calling the constructor? The constructor requires options (like the connection string), so I would need the configuration service anyway. There are other ugly hacks to get the configuration at runtime, but come on, there must be another, better and just more sane way of doing it.
I have a webAPI hosted in Azure as my back-end. I am using Code-First to create my entities. I can pre-feed my DB fine via the seed method, but if I am done with my model changes and then remember that I need to add something to the seed, there is no way to run the seed method.
Multiple posts suggest the simple run update-database. I do see: "Running Seed method." and then publish to azure, my table is still empty. And I don't want to change the models just to trigger the seed, because that is just not clean solution.
I have already tried using each of the commented lines but no avail
public MyContext() : base("name=MyContext")
{
//Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MyProject.Migrations.Configuration>("DefaultConnection"));
//Database.SetInitializer<MyContext>(new DropCreateDatabaseAlways<MyContext>());
}
is there something I should be doing at the azure side or it just plainly doesn't work unless I change one my models!?
Update1:
I already the migrations folder and follow the commands to enable it and update the db and when I have a model change, the db updates just fine. That works, no issue there. The only issue if I want to add things to the seed method and I want to execute it without having to change my model.
If you are using code first then you can manage and handle all your migrations easily.
you need to type "enable-migrations" using package manger console.After this you will have a folder named Migrations.
Check this link for additional information.
Let know if you have any issues.
All right, I'm using EntityFramework migrations, and they work fine, but when I run my application, (Web API) and the first time the context is used, the schema is created automatically for me. I DO NOT want that. I'd like to see an exception specifying that the tables do not exist or something like that.
In my configuration class I have something like this:
this.AutomaticMigrationsEnabled = false;
I thought that was going to be enough but EntityFramework keeps re-creating my database schema.
Since I'm using a Continuous Integration and Continuous Delivery process I want to use EF migrations to create the scripts for me and just check in my scripts and the scripts will be executed against the database for me (I already created that process)
So how can stop EntityFramework migrations for trying to create my database schema automatically when the application runs (when the context is accessed the first time in the AppDomain)?
Unset the database initializer for your context.
Database.SetInitializer<MyContext>(null);
I am working on a project which uses Entity Framework 4.1 for persisting our various objects to the database (code first).
I am testing in Visual Studio with a local SQL Express DB, and our Jenkins server deploys committed code to a testing server. When this happens I temporarily change my local connection string to point to the testing DB server and run a unit test to re-create the test database so that it matches our latest entities, etc.
I've recently noticed our testing server is giving this error:
The model backing the 'EntityFrameworkUnitOfWork' context has changed since the database was created. Either manually delete/update the database, or call Database.SetInitializer with an IDatabaseInitializer instance. For example, the DropCreateDatabaseIfModelChanges strategy will automatically delete and recreate the database, and optionally seed it with new data.
This is usually an indication that our code has changed and I need to run the unit test to re-create the database. Except I just did that! I don't believe there is anything wrong with our deployment process - the DLLs on the test server seem to be the same versions as in my local environment. Are there any other settings or environment factors that can cause this error about the model having changed since the database was created?
I'm new here - thanks for any help!
The error you see means that the model hash stored in EdmMetadata table is different from the model hash computed from the model in the application. Because you are running database creation from a different application (your dev. application) it is possible that those two differ. Simple advice here is: don't use different applications for database creation and instead let your main application create the database (either automatically or for example with some admin interface).
As another option you should be able to turn off this check completely by removing the convention responsible for these checks:
modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
Model hash computation is dependent on current entities in your application (any simple change result in different model hash) and on database server versions / manifest. For example a model deployed on SQL server 2005 and 2008 will have different model hash (Express vs. Full or 2008 vs. 2008 R2 should not result in different model hash).
This can happen due to reflection ordering differences across different platforms. To verify, you can use the EdmxWriter API to compare the EDMX from both environments. If any of the tables have different column ordering, then this is the issue.
To workaround, you can change the way your test database gets updated such that it is updated from your test server rather than your local box.
We are going to fix this issue in the next release.
In the code-first approach, the SSDL is generated during the execution of the code. One of the informations included in the generated SSDL is the name of the provider used in the DbConnection. As you said, you're connecting to different databases engines, so you must use two different providers. This completly changes the output of the hashing function.
The below code was extracted from the EntityFramework assembly:
using (XmlWriter writer = XmlWriter.Create(output, settings))
{
new SsdlSerializer().Serialize(database, providerInfo.ProviderInvariantName, providerInfo.ProviderManifestToken, writer);
}
This might help and the link to Scott G blog will sure be a solution to your problem check this question link
Edit 1: this is the link to Scott G blog
Edit 2: You may also check this if you use a database first on integration server
Edit 3: This is a more detailed answer like the one from Scott G
Are the two servers running your application running different operating systems (or service packs?) It appears the SHA256CryptoService used can throw a PlatformNotSupportedException which causes it to fallback to another method.
http://msdn.microsoft.com/en-us/library/system.security.cryptography.sha256cryptoserviceprovider.sha256cryptoserviceprovider.aspx
// System.Data.Entity.Internal.CodeFirstCachedMetadataWorkspace
private static SHA256 GetSha256HashAlgorithm()
{
SHA256 result;
try
{
result = new SHA256CryptoServiceProvider();
}
catch (PlatformNotSupportedException)
{
result = new SHA256Managed();
}
return result;
}
You may be able to test this by using reflection to invoke the following 2 (internal/private) methods on each server.
MetaDataWorkspace.ToMetadataWorkspace(DbDatabaseMapping, Action<string>)
CodeFirstCachedMetadataWorkspace.ComputeSha256Hash(string xml);
Entity Framework code first creates a table called EdmMetadata. It keeps a hash of your current model. Once you run the application EF checks if the model being used is the same as the model that the db 'knows about'.
If you want to perform database migration, I suggest you use EF Code first migrations though it's still an alpha.
If you don't want to use migrations you can either:
handle the schema change manually - that means moving the content of the EdmMetadata table to the test server along with all the changes
or
set the db initializer to DropCreateDatabaseIfModelChanges (or better something derived from it and use the Seed() method to write the initial data). To set the initialzer either call Database.SetInitializer() on application start or use the appSettings
<add key="DatabaseInitializerForType Fully.Qualified.Name.Of.Your.DbContext," value="Fully.Qualified.Name.Of.The.Initializer" />
I only accidentally renamed my .mdf file and got this error. So look also for this.