what is the proper way to create an EF Core database on an Android/iOS device with Xamarin.Forms and EntityFrameworkCore?
I've come across the EnsureCreated vs Migrate thing and since I'm planning on changing the database structure in the future, I'd like to be able to apply those migrations to an existing database, thus I've chosen the Migrate approach.
However, when I call the Migrate method, it doesn't seem to create a database. I've managed to make it work in case I copy the pre-generated database file on the devices beforehand, but that's not what I want. Is there a way to tell EF Core to check if there's an existing database, create a new database if not, and then apply pending migrations to it?
Here's what I've done so far:
I've created a .net standard class library "Data" that will hold all my database classes (context, models, and migrations).
I've changed the TargetFramework of the "Data" project from <TargetFramework>netstandard2.0</TargetFramework> to <TargetFrameworks>netcoreapp2.0;netstandard2.0</TargetFrameworks> so I could run PMC commands on it.
I've installed these nuget packages to the "Data" project:
EntityFrameworkCore
EntityFrameworkCore.Sqlite
EntityFrameworkCore.Tools
EntityFrameworkCore.Design
I've created a database context class. I've made two constructors for it. The default one will only be used by the PMC commands for generating migrations. The other one will be used in production.
public class MyTestContext : DbContext
{
private string _databasePath;
public DbSet<Issue> Issues { get; set; }
[Obsolete("Don't use this for production. This is only for creating migrations.")]
public MyTestContext() : this("nothing.db")
{
}
public MyTestContext(string databasePath)
{
_databasePath = databasePath;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
//I've also tried Filename={_databasePath} here, didn't work either
optionsBuilder.UseSqlite($"Data Source={_databasePath}");
}
}
I've created a test model class, just to have something to play with
public class Issue
{
public string Id { get; set; }
}
I created an inital migration using this command, which completed successfully:
Add-Migration Migration001 -Context MyTestContext -Output Migrations
I added a call to the Migrate method in the App.xaml.cs of my Xamarin.Forms project to test if it works.
public partial class App : Application{
//... other code
protected override async void OnStart()
{
base.OnStart();
using (var db = new MyTestContext(_dbPath))
{
try
{
db.Database.Migrate();
db.Issues.Add(new Issue
{
Id = "TestIssueId"
});
db.SaveChanges();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
}
//... other code
}
The _dbPath variable contains this (on Android where I'm testing it):
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "test.db");
After all this, I'm getting this exception:
Microsoft.Data.Sqlite.SqliteException: SQLite Error 1: 'no such table: Issues'.
I have a ASP.NET MVC 6 application, and i need to call the Database.EnsureCreated and Database.Migrate methods.
But where should I call them?
I think this is an important question and should be well answered!
What is Database.EnsureCreated?
context.Database.EnsureCreated() is new EF core method which ensures that the database for the context exists. If it exists, no action is taken. If it does not exist then the database and all its schema are created and also it ensures it is compatible with the model for this context.
Note:
This method does not use migrations to create the database. In addition, the database that is created cannot later be updated using migrations. If you are targeting a relational database and using migrations, you can use the DbContext.Database.Migrate() method to ensure the database is created and all migrations are applied.
How did we do that with EF 6?
context.Database.EnsureCreated() is equivalent to the below listed approaches of EF 6:
Package Manager Console:
Enable-Migrations -EnableAutomaticMigrations. Add-Migration/Update-Database.
From code:
Database.SetInitializer CreateDatabaseIfNotExists
or
With DbMigrationsConfiguration and set AutomaticMigrationsEnabled = true;
What is Database.Migrate?
Applies any pending migrations for the context to the database. Will create the database if it does not already exist.
How did we do that with EF 6?
context.Database.Migrate() is equivalent to the below listed approaches of EF 6:
Package Manager Console:
Update-Database -TargetMigration
With a custom DbMigrationsConfiguration:
AutomaticMigrationsEnabled = false; or with DbMigrator.
Conclusion:
If you are using migrations there is context.Database.Migrate(). If you don't want migrations and just want a quick database (usually for testing) then use context.Database.EnsureCreated()/EnsureDeleted().
With the information that James P and Bassam Alugili provided, what I ended up doing was to add these lines of code to the Configure method in the Startup class (Startup.cs):
using (var scope =
app.ApplicationServices.CreateScope())
using (var context = scope.ServiceProvider.GetService<MyDbContext>())
context.Database.Migrate();
Ordinarily, the DbContext will be added to the dependency injection container in Startup.ConfigureServices() like so:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add DbContext to the injection container
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(
this.Configuration.GetConnectionString("DefaultConnection")));
}
}
However, the IServiceCollection doesn't act as a service provider, and since the DbContext was not registered with the injection container before the current scope (Startup.ConfigureServices), we can't access the context through dependency injection here.
Henk Mollema discusses manually resolving services during startup here, but mentions that...
manually resolving services (aka Service Locator) is generally
considered an anti-pattern ... [and] you should avoid it as much
as possible.
Henk also mentions that the Startup constructor's dependency injection is very limited and does not include services configured in Startup.ConfigureServices(), so DbContext usage is easiest and most appropriate through the injection container used throughout the rest of the app.
The runtime's hosting service provider can inject certain services into the constructor of the Startup class, such as IConfiguration, IWebHostEnvironment (IHostingEnvironment in pre-3.0 versions), ILoggerFactory and IServiceProvider. Note that the latter is an instance built by the hosting layer and contains only the essential services for starting up an application.
In order to call Database.EnsureCreated() or Database.Migrate(), we can, and want to, have the DbContext resolve automatically in Startup.Configure(), where our configured services are now available through DI:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add DbContext to the injection container
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(
this.Configuration.GetConnectionString("DefaultConnection")));
}
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env, MyDbContext context)
{
if (env.IsDevelopment())
{
context.Database.EnsureCreated();
//context.Database.Migrate();
}
}
}
Please remember as Bassam Alugili's answer referenced from EF Core documentation that Database.EnsureCreated() and Database.Migrate() are not meant to be used together because one ensures your existing migrations are applied to the database, which is created if needed. The other just ensures a database exists, and if not, creates one that reflects your DbContext, including any seeding done through the Fluent API in the context.
Just as a foreward you should read this from Rowan Miller:
... EnsureCreated totally bypasses migrations and just creates the
schema for you, you can't mix this with migrations. EnsureCreated is
designed for testing or rapid prototyping where you are ok with
dropping and re-creating the database each time. If you are using
migrations and want to have them automatically applied on app start,
then you can use context.Database.Migrate() instead.
According to answer here you need to add Globals.EnsureDatabaseCreated(); it to Startup.cs:
Startup function in Startup.cs:
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
if (env.IsDevelopment())
{
// This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
builder.AddApplicationInsightsSettings(developerMode: true);
}
Configuration = builder.Build();
Globals.Configuration = Configuration;
Globals.HostingEnvironment = env;
Globals.EnsureDatabaseCreated();
}
And define Globals.EnsureDatabaseCreated() as follows:
public static void EnsureDatabaseCreated()
{
var optionsBuilder = new DbContextOptionsBuilder();
if (HostingEnvironment.IsDevelopment()) optionsBuilder.UseSqlServer(Configuration["Data:dev:DataContext"]);
else if (HostingEnvironment.IsStaging()) optionsBuilder.UseSqlServer(Configuration["Data:staging:DataContext"]);
else if (HostingEnvironment.IsProduction()) optionsBuilder.UseSqlServer(Configuration["Data:live:DataContext"]);
var context = new ApplicationContext(optionsBuilder.Options);
context.Database.EnsureCreated();
optionsBuilder = new DbContextOptionsBuilder();
if (HostingEnvironment.IsDevelopment()) optionsBuilder.UseSqlServer(Configuration["Data:dev:TransientContext"]);
else if (HostingEnvironment.IsStaging()) optionsBuilder.UseSqlServer(Configuration["Data:staging:TransientContext"]);
else if (HostingEnvironment.IsProduction()) optionsBuilder.UseSqlServer(Configuration["Data:live:TransientContext"]);
new TransientContext(optionsBuilder.Options).Database.EnsureCreated();
}
To use context.Database.Migrate() see here or here.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddDbContext<YourDbContext>(option => option.UseSqlServer(#"Data source=(localdb)\ProjectModels;Initial Catalog=YourDb;Integrated Security=True"));
var app = builder.Build();
// Configure the HTTP request pipeline.
YourDbContext dbcontext = app.Services.GetRequiredService<YourDbContext>();
dbcontext.Database.EnsureCreated();
Additionally you may see a performance hit if you call this in the constructor of your context... After moving EnsureCreated to the setup.cs utility, I noticed considerable improvements to my response times.
Note: I am using EFC and UWP.
If you are working with VS 2022 / .Net Version 6 and you are trying to find a way to create your database..then
Do these following steps
Add Microsoft.EntityFramework.Tools reference through Package manager
from Package Manager Console
Run Step 1
Add-Migration InitialMigration
InitialMigration here is custom name you can type anything you want..
let it run
Step 2
Update-Database
This should create your database.
I am developing ASP .NET Core 6 Web API.
In Program.cs after building the app I wrote the code below.
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
using var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
context.Database.EnsureCreated();
}
context.Database.EnsureCreated() ensures that the database for the context exists.
If the database exists and has any tables, then no action is taken. Nothing is done to ensure the database schema is compatible with the Entity Framework model.
If the database exists but does not have any tables, then the Entity Framework model is used to create the database schema.
If the database does not exist, then the database is created and the Entity Framework model is used to create the database schema.
I hope I helped.
I am playing with ASP.NET 5 project using EF7. I have created basic testing model, made code-first migrations, database updates and now I try to send some data to created database.
I am using the default DbContext (ApplicationDbContext.cs) included with project tepmplates. When I have tried to send data with controller, everything worked. Now I want to seed database on start. To do this, I have created seed extension method to ApplicationDbContext:
public static class ProjectExtensions
{
public static void SeedDB(this ApplicationDbContext context)
{
context.Add(new TestingModel() { Name = "foo" });
context.SaveChanges();
}
}
Then, I have added method calling inside Startup.cs in Configure method:
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
using (var context = new ApplicationDbContext())
{
context.SeedDB();
}
}
But when I try to run the project, system throws an exception:
An exception of type 'System.InvalidOperationException' occurred in EntityFramework.Core.dll but was not handled in user code
Additional information: No database providers are configured. Configure a database provider by overriding OnConfiguring in your DbContext class or in the AddDbContext method when setting up services.
It seem strange, becuase database provider should be already configured. As I said, I am using default context with default settings. And when I call controller to make some CRUD operation, everything is working.
Does anyone know, where is a mistake?
Found solution. All seems to be fixed with this edit inside the Startup.cs in Configure method:
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
.CreateScope())
{
serviceScope.ServiceProvider.GetService<ApplicationDbContext>().Database.Migrate();
serviceScope.ServiceProvider.GetService<ApplicationDbContext>().SeedDB();
}
}
Hope this could help someone with the same problem :)
I'm in the process of adding new fields, on my class but I've noticed the table won't be created until I save an object of that class. It's a bit annoying, is there a way to force it to create that table on App start or something?
You can force the initializer at Application_Start by
protected void Application_Start()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyContext>());
(new MyContext()).Database.Initialize(true);
}
I think you can use Repository and Unit of Work patterns.
Using Repository and Unit of Work patterns with Entity Framework 4.0
which the Save method is defined in IUnitOfWork interface.
public interface IUnitOfWork
{
void Save();
}
I'm using Entity Framework 4.3 Code First with a custom database initializer like this:
public class MyContext : DbContext
{
public MyContext()
{
Database.SetInitializer(new MyContextInitializer());
}
}
public class MyContextInitializer : CreateDatabaseIfNotExists<MyContext>
{
protected override void Seed(MyContext context)
{
// Add defaults to certain tables in the database
base.Seed(context);
}
}
Whenever my model changes, I edit my POCO's and mappings manually and I update my database manually.
When I run my application again, I get this error:
Server Error in '/' Application.
The model backing the 'MyContext' context has changed since the database was created. Consider using Code First Migrations to update the database (http://go.microsoft.com/fwlink/?LinkId=238269).
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The model backing the 'MyContext' context has changed since the database was created. Consider using Code First Migrations to update the database (http://go.microsoft.com/fwlink/?LinkId=238269).
Using EFProfiler, I also notice these queries being executed:
-- statement #1
SELECT [GroupBy1].[A1] AS [C1]
FROM (SELECT COUNT(1) AS [A1]
FROM [dbo].[__MigrationHistory] AS [Extent1]) AS [GroupBy1]
-- statement #2
SELECT TOP (1) [Project1].[C1] AS [C1],
[Project1].[MigrationId] AS [MigrationId],
[Project1].[Model] AS [Model]
FROM (SELECT [Extent1].[MigrationId] AS [MigrationId],
[Extent1].[CreatedOn] AS [CreatedOn],
[Extent1].[Model] AS [Model],
1 AS [C1]
FROM [dbo].[__MigrationHistory] AS [Extent1]) AS [Project1]
ORDER BY [Project1].[CreatedOn] DESC
How can I prevent this?
At first I was sure it was because you set the default initializer in the ctor but investigating a bit I found that the initializer isn't run when the context is created but rather when you query/add something for the first time.
The provided initializer all check model compability so you are out of luck with them. You can easily make your own initializer like this instead though:
public class Initializer : IDatabaseInitializer<Context>
{
public void InitializeDatabase(Context context)
{
if (!context.Database.Exists())
{
context.Database.Create();
Seed(context);
context.SaveChanges();
}
}
private void Seed(Context context)
{
throw new NotImplementedException();
}
}
That shouldn't check compability and if the Database is missing it will create it.
UPDATE: "Context" should be the type of your implementation of DbContext
Pass null to System.Data.Entity.Database's
public static void SetInitializer<TContext>(
IDatabaseInitializer<TContext> strategy
)
where TContext : DbContext
to disable initialization for your context. Don't implement IDatabaseInitializer to disable it.
https://msdn.microsoft.com/en-us/library/system.data.entity.database.setinitializer(v=vs.113).aspx