Xamarin.Forms and EntityFrameworkCore 3.x how to properly create a database and apply migrations? - sqlite

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'.

Related

How to enable migration in EntityFramework Core Sqlite

I have Dbset and creating database like this. When first time db is created then after I add column in dbset, then the migration does not work the table column is not found. Please help me how I can enable EF Core Sqlite migrations in Xamarin Forms.
public BaseSQLRespository(string databasePath)
{
try
{
_databasePath = databasePath;
Database.EnsureCreated();
// bool IsDatabaseCreated= ;
// Settings.IsDatabaseCreatedSettings = IsDatabaseCreated;
}
catch (Exception ex)
{
throw ex;
}
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
try
{
optionsBuilder.UseSqlite(string.Format("Filename={0}", _databasePath));
}
catch (Exception ex)
{
throw ex;
}
}
Assuming you created a migration for your new column using the add-migration command, you can enable the execution of migrations at runtime by calling the Migrate() method instead of EnsureCreated(). The docs for the method can be found here.
As per Microsoft's documentation, the Migrate method is incompatible with the EnsureCreated method which actually bypasses the migrations when creating the database schema - so in your case you will have to delete the old database (uninstall the app or clear it's data) before trying the new code.
Don't call EnsureCreated() before Migrate(). EnsureCreated() bypasses Migrations to create the schema, which causes Migrate() to fail.
Source: EF Core - Apply migrations at runtime
To add an initial migration, run the following command:
dotnet ef migrations add YourNameOfMigration
Next, apply the migration to the database to create the schema:
dotnet ef database update
Migrations
Every time when you add a new field to the model you should add migration and apply it to DB.
i guess your configuration is wrong, you shouldn't call Database.EnsureCreated(); in ctor because OnConfiguring() is not called yet and your database is not initialized.
you can create a method which called once your serviceProvider is started.
public static void Initialize(IServiceProvider serviceProvider)
{
var context = serviceProvider.GetRequiredService<BaseSQLRespository>();//use your service provider any thing which you can get your current created `BaseSQLRespository`
context.Database.EnsureCreated();//once call when your app is started. (it is up to you which method you used to call)
...
...
}
In Addition, here is similar topic and different solution,
you can check: Error in Database.EnsureCreated() while using Entity Framework with Sqlite in Xamarin

Deployed SQLite database is empty after deployment of Universal App

I use in SQLite database in UWP. It has a few tables and is located in Assets folder and has build action marked as "Content".
Under development machine it works fine.
But after deployment to MS Windows 10 tablet it has the error
SQLite error 1 no such table Companies
It corresponds to the code
using (MobileContext db = new MobileContext())
{
companiesList.ItemsSource = db.Companies.ToList();
...
}
var createdResult = Database.EnsureCreated(); // It gives false so
I assume based on https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.infrastructure.databasefacade.ensurecreated?view=efcore-2.1 that database exists.
I have tried
optionsBuilder.UseSqlite("Data Source=" + ApplicationData.Current.LocalFolder.Path + #"\Mobile.db");
optionsBuilder.UseSqlite("Data Source=Mobile.db");
Any clue folk? Thanks!
P.S. I use Microsoft.EntityFrameworkCore to accees SQLite.
P.S. #2 I have tried this solution https://social.msdn.microsoft.com/Forums/en-US/1a933b13-09ee-46ec-9045-e2f567b6048c/uwp-sqlite-error-1-no-such-table-name-table?forum=wpdevelop but it does not work.
Derive from official document,
Connection strings in a UWP application are typically a SQLite connection that just specifies a local filename. They typically do not contain sensitive information, and do not need to be changed as an application is deployed. As such, these connection strings are usually fine to be left in code, as shown below. If you wish to move them out of code then UWP supports the concept of settings
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=blogging.db");
}
}
And the default path of db file is application LocalFolder. It looks like this C:\Users\vxx\AppData\Local\Packages\e045f456-27d9-4966-b639-01e2281b249f_7jxxxxxxxxxx\LocalState. If your configuration is same as above, when deploy in new machine, the content of db file is empty.
OP Update
I just commented some decorations of the class like [Table("Companies")] and [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] and it works now!

DropCreateDataBaseAlways is not working when working with multiple db schemas with Entity Framework 6 Code First

After watching the "Enhancements to Code First Migrations: Using HasDefaultSchema and ContextKey for Multiple Model Support" section of Julie Lerman's PluralSite video, "Entity Framework 6: Ninija Edition-What's New in EF 6" (https://app.pluralsight.com/library/courses/entity-framework-6-ninja-edition-whats-new/table-of-contents), it seems there is a way to run multiple schemas under a single database in Entity Framwork 6 using Code First Migrations...
However, based on the video you still need to these package manager commands for each project that houses a separate context:
1. enable-migrations
2. add-migration [MIGRATION NAME]
3. update-database
This is fine and good if you actually care about maintaining migrations going forward, which is not a concern of mine.
What I'd like to do is have each of my Context's initializers set to DropCreateDatabaseAlways, and when I start up my client app (in this case, an MVC site), code first will create the database for the first context used, create the tables in with the correct schema for that context, and then create the tables for the rest of the contexts with the correct schema.
I don't mind if the whole database is dropped and recreated every time I hit F5.
What is happening now is the last context that is accessed in the client app is the only context tables that are created in the database... any contexts being accessed before the last get their tables blown away.
I am currently using two contexts, a Billing context and a Shipping context.
Here is my code:
My client app is an MVC website, and its HomeController's Index method looks like this:
public ActionResult Index()
{
List<Shipping.Customer>
List<Billing.Customer> billingCustomers;
using (var shippingContext = new Shipping.ShippingContext())
{
shippingCustomers = shippingContext.Customers.ToList();
}
using (var billingContext = new Billing.BillingContext())
{
billingCustomers = billingContext.Customers.ToList();
}
}
Here is my DbMigrationsConfigurationClass and ShippingContext class for the Shipping Context:
internal sealed class Configuration : DbMigrationsConfiguration<ShippingContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(ShippingContext context)
{
}
}
public class ShippingContext : DbContext
{
public ShippingContext() : base("MultipleModelDb")
{
}
static ShippingContext()
{
Database.SetInitializer(new ShippingContextInitializer());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("Shipping");
base.OnModelCreating(modelBuilder);
}
public DbSet<Customer> Customers { get; set; }
class ShippingContextInitializer : DropCreateDatabaseAlways<ShippingContext>
{
}
}
Likewise, here is the DbMigrationConfiguration class for the Billing Context and the BillingContext class:
internal sealed class Configuration : DbMigrationsConfiguration<BillingContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(BillingContext context)
{
}
}
public class BillingContext : DbContext
{
public BillingContext() : base("MultipleModelDb")
{
}
static BillingContext()
{
Database.SetInitializer(new BillingContextInitializer());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("Billing");
base.OnModelCreating(modelBuilder);
}
public DbSet<Customer> Customers { get; set; }
class BillingContextInitializer : DropCreateDatabaseAlways<BillingContext>
{
}
}
based on the order that the contexts are being called in the controller's action method, whichever context is accessed last is the only context that is created... the other context is wiped out.
I feel like what I'm trying to do is very simple, yet code first migrations, as well as trying to "shoehorn" Entity Framework to represent multiple contexts as separate schemas in the same physical database seems a bit "hacky"...
I'm not that versed with migrations to begin with, so what I'm trying to do might not make any sense at all.
Any feedback would be helpful.
Thanks,
Mike

I'm getting a cryptic error connecting a new MVC app to an Azure DB created by mobile services

Like the title says, I am trying to connect a new MVC 5 app created by Visual Studio 2013 to an existing database table created by and Azure Mobile Service.
I realized that the issue was with the schema of the tables that were created by the mobile service. Instead of using the default "dbo" schema, the tables used <mobile_service_name> as the schema. My MVC project was looking for dbo.<Table_Name> instead of <mobile_service_name>.<Table_Name> and was throwing an error as a result. To fix this, you need to add some mappings in you DB Context class to tell it where exactly to find the tables it is looking for. These mappings are done in an overridden method called OnModelCreating. It ends up looking like this:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext() : base("DefaultConnection")
{
}
public System.Data.Entity.DbSet<MyObject> MyObjects { get; set; }
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<MyObject>().ToTable("<mobile_service_name>.<Table_Name>");
}
}

asp.net mvc migrations generates empty migrations files

After adding new Migration I am getting file generated by vs 2012 but the file is empty , despite I have a changes in "Table" class.
public override void Up()
{
}
public override void Down()
{
}
Anyone faced with this issue ?
This generally indicates the Entity Framework did not detect any changes in your model. For this to happen you have to ensure two things:
Some class in your \models folder changes, (add a field, a data
annotation, etc)
You must add a DbSet to you config. Do a file
search on DbSet and make sure you add a line like this:
public System.Data.Entity.DbSet<YourProject.Models.YourClass> YourDbSetName { get; set; }

Resources