I am trying to use EF Core 3.0 migrations with a hybrid of an existing tables and new tables built with code first. To prevent the scaffolding of the existing tables I would like to decorate the model class with an attribute (fluently or annotations) so that the migration code generation for those tables is skipped but the model is still built into the DbContext class.
The approach I'm taking is to add the following lines to OnConfiguring
optionsBuilder.ReplaceService<IMigrationsSqlGenerator, SkipMigrator>();
And then creating a SkipMigrator with the following code
public class SkipMigrator:SqlServerMigrationsSqlGenerator
{
public SkipMigrator(
MigrationsSqlGeneratorDependencies dependencies,
IMigrationsAnnotationProvider migrationsAnnotations)
: base(dependencies, migrationsAnnotations){}
protected override void Generate(
MigrationOperation operation,
IModel model,
MigrationCommandListBuilder builder)
{
if (operation.FindAnnotation("SkipMigrations")!=null)
{
Console.WriteLine("Skipping table:");
}
else
{
base.Generate(operation,model,builder);
}
}
}
I assumed that the Generate method was what triggered the creation of the migration code file but it never gets called. Id there a different place I should be intercepting the code generation?
If there a different/simpler way to tell migrations to skip tables yet still keep them in my DbContext?
Your means of trying to create your own IMigrationsSqlGenerator was correct, and I've used that approach before to alter the SQL that is generated as part of a migration.
services.AddDbContext<MyDbContext>(opt =>
{
opt.UseSqlServer();
opt.ReplaceService<IMigrationsSqlGenerator, SkipMigrator>();
});
However, as of EF Core 5.0 it's now much easier to exclude specific tables from migrations using the ExcludeFromMigrations() method on a TableBuilder:
public class ReportingContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().ToTable(nameof(Users), t => t.ExcludeFromMigrations());
}
}
More info: https://devblogs.microsoft.com/dotnet/announcing-entity-framework-core-efcore-5-0-rc1/#exclude-tables-from-migrations
If there a different/simpler way to tell migrations to skip tables yet still keep them in my DbContext?
Yes, but it requires a different approach.
Instead of using your current DbContext class for creating migrations, create a SecondDbContext class only for the very purpose of creating migrations. This new SecondDbContext class will hold the DbSets<T> that you want EF to do its migrations on.
Then simply specify the second context when calling add-migration UpdateTable -c SecondDbContext and then update-database -c SecondDbContext.
Related
Update 8/29/18
Seeing this issue in inline new-ing of seed data as well. Opened an EF Core issue. Will update the question with any findings.
I am trying to use EF Core 2.1's seeding mechanism. However, I want to load the seed data from json flat files rather than hard-coding new C# objects. I wrote the extension method below, which worked well for the initial migration. It looks for files named [MyEntity].json in DataPath and deserializes them into objects.
The problem is that if I add a subsequent migration, even without changing a single thing in the models, configurations, or json files, the new migration deletes every single entity and re-inserts them again. So, the Up() contains a bunch of deletes followed by a bunch of inserts of the same data. I suspect this is because EF Core is not smart enough to recognize that it is an identical set of seed data.
Question:
Is there a way to use EF Core 2.1 seeding with an external data source (such as json files) without having each migration delete and re-insert the data?
My seeding extension method:
public static class ModelBuilderExtensions
{
public static string DataPath { private get; set; } = "..\\..\\data";
public static void Seed<TEntity>(this ModelBuilder modelBuilder) where TEntity : class, IBaseEntity
{
var entities = GetSeedRows<TEntity>();
modelBuilder.Entity<TEntity>().HasData(entities);
}
private static TEntity[] GetSeedRows<TEntity>() where TEntity : IBaseEntity
{
try
{
return JsonConvert.DeserializeObject<TEntity[]>(
File.ReadAllText(DataPath + Path.DirectorySeparatorChar + typeof(TEntity).Name + ".json"));
}
catch (FileNotFoundException e)
{
Console.WriteLine(e.Message);
return null;
}
}
}
This issue was an EF Core bug that was resolved in EF Core 2.2.
I have separated my solution in separate projects, a DAL project with entity framework and an ASP.NET MVC project.
I want to use DropCreateDatabaseIfModelChanges, but I don't know where to put it to make it work.
I've tried to put it in the web.config of the MVC project and the app.config of the DAL project (both by making use of the context element), I've tried putting it in the global.asax (Database.SetInitializer(new DropCreateDatabaseIfModelChanges<BreakAwayContext>());), I've tried a custom initialization class, but none of these seem to work.
If possible, I don't want to make use of migrations. How can I make it work?
You could create a class to implement CreateDatabaseIfNotExists and call Database.SetInitializer function in Application_Start().
-DbInitializer
public class MyDbInitializer : CreateDatabaseIfNotExists<MyDbContext>
{
protected override void Seed(MyDbContext context)
{
//Data initializing...
}
}
-Application_Start
protected void Application_Start()
{
Database.SetInitializer(new MyDbInitializer());
}
The database will be create when running the application.
And if you would like to do a automatic migration of database, use MigrateDatabaseToLatestVersion class
public class Configuration : DbMigrationsConfiguration<MyDbContext>
{
public Configuration()
{
this.AutomaticMigrationsEnabled = true;
}
}
-Application_Start
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext,Configuration>());
Howerver, I recomand that using migraion commands will be more flexible. See this walkthru: Overview of Entity Framework Code First Migrations with example, by Bhavik Patel.
I guess by 'database initialization' you actually mean 'updating the database schema'.
Set the EfRepository as start up project of the solution
Open the Package manager console Choose EfRepository as default project
Run the following commands:
Enable-Migrations -ConnectionStringName "EfDataRepository"
Add-Migration Initial -ConnectionStringName "EfDataRepository"
Update-Database -ConnectionStringName "EfDataRepository" -Script -SourceMigration:0
This will give you a .sql script. Execute it against your database (and usually store it as part of the solution - either Create.sql or some kind of a migration .sql, depends on whether you already have a schema or you are creating it from scratch).
Of course, replace EfDataRepository with the data connection name from your .config file.
I'm using WAF ( WPF Application Framework ) in here: https://waf.codeplex.com.
And I open the BookLibrary project in it sample.
I have an model named Author and it related class.
And this is it DbContext:..
internal class BookLibraryContext : DbContext
{
public BookLibraryContext(DbConnection dbConnection)
: base(dbConnection, false)
{
Database.SetInitializer<BookLibraryContext>(null);
}
public BookLibraryContext()
: base(#"Data Source=|DataDirectory|\Resources\BookLibrary2.sdf")
{
}
public bool HasChanges
{
get
{
ChangeTracker.DetectChanges();
// It is necessary to ask the ObjectContext if changes could be detected because the
// DbContext does not provide the information when a navigation property has changed.
return ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Any()
|| ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Any()
|| ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted).Any();
}
}
private ObjectContext ObjectContext { get { return ((IObjectContextAdapter)this).ObjectContext; } }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new PersonMapping());
modelBuilder.Configurations.Add(new BookMapping());
modelBuilder.Configurations.Add(new AuthorMapping());
}
}
When i run the project. An exception occured:
{"The specified table does not exist. [ Author ]"}
How do i add new table named Author ? I know using Entity Framework migration or edit the database structor with tools.
But I see the method named HasChange(). It may do something to reflect my database. But I don't know to make it work. Please help me
You are using Database.SetInitializer<BookLibraryContext>(null); which causes Entity Framework to do no initialization when starting up, meaning your Data needs to already match your classes.
You can use the following initializers instead of null:
CreateDatabaseIfNotExists. This will create a new database if none exists. However, changing the model will not recreate the database, and the program will error.
DropCreateDatabaseIfModelChanges This will create a new database if the model changes, or leave the existing database if the model has stayed the same.
DropCreateDatabaseAlways This will create a new database every time the program is run.
MigrateDatabaseToLatestVersion This will process Entity Framework Migrations, to preserve the data in the database and add or remove tables as necessary (also called Data Motion).
Migrations are the hardest to set up, but the easiest to maintain, in the long run. However, for testing, any option would be fine.
An example of using one of the initializers would be:
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<BookLibraryContext>());
I am using EF5 with the DefaultMembershipProvider and want to control the schema used in SQL Server for the tables created by the DefaultMembershipProvider.
EF5 says this is doable as:
modelBuidler.Entity<MyEntity>().ToTable("MyTable", "MySchema");
but as these are not "my" entities I cannot do it this way.
Questions:
1) So how do I do this in EF5?
2) Is this dealt with in EF6 when using DbModelBuilder.HasDefaultSchema?
Yes indeed I would upgrade to EF6 and then make your context look like this, e.g.:
public partial class BlogContext : DbContext
{
public BlogContext()
: base("BlogDb")
{
Database.SetInitializer<BlogContext>(null);
}
public DbSet<BlogPost> BlogPosts { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingEntitySetNameConvention>();
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.HasDefaultSchema("");
}
}
You see setting the default schema at the end to "", which also works, in my case, Oracle 12c takes the login user name as schema and everything works fine. Of course you could also load the schema name from configuration and insert it there.
Note: Automatic migrations will NOT work anymore, as this seems to confuse the system quite much. See here for a possible solution so that at least explicit migrations will work somehow: Entity Framework using IdentityDbContext with Code First Automatic Migrations table location and schema?
I created a DbContext like so :
public class myDB : DbContext
{
public DbSet<Party> Parties { get; set; }
public DbSet<Booking> Bookings { get; set; }
}
This generated my DataBase and the two tables above..Great
I then decided to add another DbSet into the mix & I got an error:
the model backing the 'Party' context has changed since the database was created
I'm aware of the fixes for this using modelbuilder.IncludeMetadataInDatabase = false; and Database.SetInitializer<ClubmansGuideDB>(null);
1) What's the correct way to add my new classes to the context and have them generated in the DataBase?
2) In my futile attempts to solve this myself I deleted the tables and tried to re-run the app with no success I then deleted the full database and re-run and it doesn't re-generate the DB. How can I start from scratch - is there some cache somewhere?
I believe you're looking for:
Database.SetInitializer<ClubmansGuideDB>(new DropCreateDatabaseIfModelChanges<ClubmansGuideDB>());
As of EF 4.1 and above.
Note that this assumes you have permission to even drop your database. I do this locally for ease of development but disable it in staging/production (I do a manual Schema Compare and push my changes).
By the way, for testing, if you need to force recreate the database, you can do:
using (var context = new ClubmansGuideDB()) {
context.Database.Initialize(force: true);
}
(using if you don't already have a reference to your DB)
You can let the Entity Framework recreate the whole database by using a Database Initializer or if you want to migrate data you can look at the Code First Migrations
The following would recreate your database when the model changes:
Database.SetInitializer<myDB>(new DropCreateDatabaseIfModelChanges<myDB>());
Try putting this in your Global.asax.cs Application_Start() method.
Database.SetInitializer<DatabaseContext>(null);
To reset the database from scratch on app run make a class like this
//Where myDB is your context
public class EntityBase: DropCreateDatabaseAlways<myDB>
{
}
Then in your Application_Start() method you can do this
Database.SetInitializer(new EntityBase());