I have two databases. When I make a change to one of my dbcontext (adding a new DbSet) it should automatically apply the changes to the correct databases when runnnig my webapplication. So the new table should be added. Therefore I have added two configuration classes. One for each database/context.
However using the initializers like below the changes are always applied to the second context/database. Because this is the latest initializer configured.
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DomainReadModelContext, DomainConfiguration>());
Database.SetInitializer(new MigrateDatabaseToLatestVersion<WebsiteReadModelContext, WebsiteConfiguration>());
I also tried it in the web.config
<contexts>
<context type="Domain.ReadModels.DomainReadModelContext, Domain.ReadModels">
<databaseInitializer type="System.Data.Entity.MigrateDatabaseToLatestVersion`2[[Domain.ReadModels.DomainReadModelContext, Domain.ReadModels], [Website.Migrations.Domain.DomainConfiguration, Website-WebAppMainModule, Version=1.0.0.0, Culture=neutral]], EntityFramework" />
</context>
<context type="Website.ReadModels.WebsiteReadModelContext, Website.ReadModels">
<databaseInitializer type="System.Data.Entity.MigrateDatabaseToLatestVersion`2[[Website.ReadModels.WebsiteReadModelContext, Website.ReadModels], [Website.Migrations.Website.WebsiteConfiguration, Website-WebAppMainModule, Version=1.0.0.0, Culture=neutral]], EntityFramework" />
</context>
</contexts
When applying the changes via the package manager it works as it should be. The table gets added to my domaincontext database.
Update-Database -config DomainConfiguration
Is this because this isn't supported or am I doing it wrong? Now it seems to work only for the latest initializer registered.
For the update I have scaffolded a Migration using the Add-Migration command in the package manager.
Add-Migration AddUniquePersonReadModelMigration -config DomainConfiguration
This generated following class for me.
public partial class AddUniquePersonReadModelMigration : DbMigration
{
public override void Up()
{
CreateTable(
"UniquePersonReadModels",
c => new
{
Id = c.Guid(nullable: false, identity: true),
PersonId = c.Guid(nullable: false),
DisplayName = c.String(maxLength: 128),
})
.PrimaryKey(t => t.Id)
.Index(p => p.PersonId)
.Index(p => p.DisplayName, true);
}
public override void Down()
{
DropIndex("UniquePersonReadModels", new[] { "PersonId" });
DropIndex("UniquePersonReadModels", new[] { "DisplayName" });
DropTable("UniquePersonReadModels");
}
}
So my question is does entity framework support the migrations for multiple contexts using initializers? If not, it would be a nice feature when the migrations can be handled for multiple contexts.
I have solved the problem by moving the Migration folder from my website to separate assemblies.
So instead of the following:
Solution/
DomainReadModels/
DomainContext.cs
Classes........
WebsiteReadModels/
WebsiteContext.cs
Classes........
Website/
Migration/
AddSomeTableToDomainMigration.cs
AddSomeTableToWebsiteMigration.cs
WebsiteReadModelsConfiguration.cs
DomainReadModelsConfiguration.cs
Websitefiles...
I changed my solution to:
Solution/
DomainReadModels/
Migration/
DomainReadModelsConfiguration.cs
AddSomeTableToDomainMigration.cs
DomainContext.cs
Classes........
WebsiteReadModels/
Migration/
AddSomeTableToWebsiteMigration.cs
WebsiteReadModelsConfiguration.cs
WebsiteContext.cs
Classes........
Website/
Websitefiles...
Now the only disadvantage is I have to switch projects in the package manager console...
If I understand your question correctly you are using two contexts DomainReadModelContext and WebsiteReadModelContext and each of these contexts uses a different database. If this is the case, then you can certainly have an initializer for each context and it will be run the first time that context is used against the given database. In other words, the initializer for DomainReadModelContext will be run the first time that DomainReadModelContext is used against the domain model database, and the initializer for WebsiteReadModelContext will be run the first time that WebsiteReadModelContext is used against the domain model database.
If both contexts are using the same database then this will not work. Using Migrations for multiple contexts that access the same database is not supported in EF5, but we're working on it for EF6.
Related
I'm using Masstransit for the first time, and can't make my sag's Persistable. Following the documented saga sample (EF Core): https://masstransit-project.com/usage/sagas/efcore.html
Generating the Initial migration fails, being quite new to both EF and masstransit I'm lost.
Looks like DI can't find the correct DbContextOptions
PM> dotnet ef migrations add InitialCreate -c OrderStateDbContext
Build started...
Build succeeded.
Unable to create an object of type 'OrderStateDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
MassTransit.EntityFrameworkCore v7.1.7
Microsoft.EntityFrameworkCore.SqlServer v5.0.4
Microsoft.EntityFrameworkCore.Design v5.0.4
Code;
collection.AddMassTransit(x =>
{
x.AddSagaStateMachine<OrderStateMachine, OrderState>()
.EntityFrameworkRepository(r =>
{
r.ConcurrencyMode = ConcurrencyMode.Pessimistic; // or use Optimistic, which requires RowVersion
r.AddDbContext<DbContext, OrderStateDbContext>((provider, builder) =>
{
builder.UseSqlServer(connectionString, m =>
{
m.MigrationsAssembly(Assembly.GetExecutingAssembly().GetName().Name);
m.MigrationsHistoryTable($"__{nameof(OrderStateDbContext)}");
});
});
});
public class OrderStateDbContext :
SagaDbContext
{
public OrderStateDbContext(DbContextOptions options)
: base(options)
{
}
protected override IEnumerable<ISagaClassMap> Configurations
{
get { yield return new OrderStateMap(); }
}
}
Appreciate any help
Duncan
First, you are reading the old v6 documentation (which was preserved for those on v6). You should be referencing the current documentation.
Second, I don't see any issue with the two types, the context should be available in the assembly. For a working example, look at ForkJoint.
It registers the DbContext separately and uses that DbContext in the saga repository, but it should work either way.
I have an applied migration using "haward" db schema.
public partial class CreateCourseCategoryTable : DbMigration
{
public override void Up()
{
CreateTable(
"haward.CourseCategories",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
Code = c.String(),
})
.PrimaryKey(t => t.Id);
}
public override void Down()
{
DropTable("haward.CourseCategories");
}
}
with this mapping
public class CourseCategoryMapping : EntityTypeConfiguration<CourseCategory>
{
public CourseCategoryMapping()
{
ToTable("CourseCategories", "haward");
}
}
now I want to change the schema from "haward" to "tr"
I dont want to add migration with this one so I thought of just editing directly the source code of the Migration and Mapping.
public partial class CreateCourseCategoryTable : DbMigration
{
public override void Up()
{
CreateTable(
"tr.CourseCategories",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
Code = c.String(),
})
.PrimaryKey(t => t.Id);
}
public override void Down()
{
DropTable("tr.CourseCategories");
}
}
public class CourseCategoryMapping : EntityTypeConfiguration<CourseCategory>
{
public CourseCategoryMapping()
{
ToTable("CourseCategories", "tr");
}
}
then recreate empty database and issued the command update-database
but its telling that I still have pending changes.
so what I did was issued the add-migration command to check which changes are those. and it seems like it still detects my edits(from "haward" to "tr" schema) even without the migrations table.
where do the model changes being saved? and how to edit directly the source code and reapply the migration? I know its not advisable because thats what migration is for. but I dont want to get my history dirty with just those changes specially if I am only at early development stage.
TL;DR: This is very complicated - it's a lot easier just to add a new migration later to correct the problem.
An Entity Framework migration consists of two parts - the code, and a hash of the model. The hash of the model is used to determine whether the model has changed, and hence whether any new migrations are required.
If you change the model, you change the hash. This hash is stored in the MigrationName.designer.cs file. You cannot just edit the model and change the migration.cs code, as the model no longer matches the model hash. You also need to regenerate the hash for the model.
The only way to do this is to roll your database back, and update the hash.
Consider you have 3 migrations applied:
Migration1
Migration2
Migration3
If you want to apply a change from Migration2 onwards...
Roll back to Migration1: Update-Database -TargetMigration Migration1 -Force (NB - This may cause data loss, so work on a development copy of your database)
Make your model code match what you wanted for Migration 2, and update the code for Migration 2 by hand
Regenerate the designer file for Migration2: Add-Migration xxxxxxxxxxx_Migration2 (use the full name of the migration including the date). This will only update the designer.cs file
Apply Migration2: Update-Database -TargetMigration Migration2
Reapply any model changes to your code for Migration3
Regenerate the designer file for Migration3: Add-Migration xxxxxxxxxxx_Migration3
Update the database to latest: Update-Database
I'm not sure if this helps anyone else, but I was able to edit and re-apply a migration by performing the following:
Delete the record of the migration from the _MigrationHistory table. Find the one with a "migrationId" that matches your migration class file name and delete it.
Manually undo all of the previous migration's changes (which would have been everything in the Down() section of your migration class file before you added the new stuff).
Update-Database (or whatever you do to apply your migrations. I have auto-migration turned on so I just run my app).
My migration was pretty simple so I'm not sure if this will work for everyone.
Enjoy!
There's no easy way of doing this but, if you're using a version control system (Git, Subversion, etc.), there's another option. It's a bit more toilsome but was the only one that worked for me.
Considering you have the following migrations:
Migration1
Migration2
Migration3
Assuming you want to change Migration2, you could follow the steps below:
Create a patch with the changes you want to be applied to Migration2
Check out the commit immediately before the migration you want to avoid (in this case, Migration3 - It will look like you have just created Migration2)
Update the DB to Migration1 - Update-Database -TargetMigration Migration1 -Force
Apply the patch you created on step 1
Recreate Migration2 - Add-Migration Migration2 (it will now contain exactly the changes you want)
Copy the migration files created to a different directory
Revert all the uncommitted changes
Check out the HEAD commit again
Delete the files for Migration2 and Migration3
Copy the files from step 6 back to their original directory
Update the database - Update-Database
Recreate Migration3 - Add-Migration Migration3
Update the database again - Update-Database
I assume, this will be the simple one to followed.
Remove the migration from the code base
and Add the same migration after updating the model class.
Then to update-database command.
This will work.
Initially our solution had System.Data.Entity.Infrastructure.LocalDbConnectionFactory set as the defaultConnectionFactory type in our web.config.
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="v11.0" />
</parameters>
I'm not sure we really need it like that since we use local SQL Server for dev and SQL Azure for real deployments. We use EF Code First and our tables get created with keys named like PK_dbo.Customers and FK_dbo.Customers_dbo.Employers_EmployerID and an index is created for each foreign key like IX_EmployerID.
We have switched to a custom connectionfactory based on the ideas in this post for a ReliableDbProvider created by Robert Moore because we want to have built in retry logic for transient failures with SQL Azure. It seems to work fine but it also seems like it causes the keys to be named differently (PK__Customer__A4AE64B8BB3388DF, Customer_Employer) and indexes to not be generated.
I didn't expect the factory to influence the generation. Any idea how it contributes?
After reflecting some code, seems like it has to do with the way the DbMigrationsConfiguration class which is used inside the DropCreateDatabaseIfModelChanges initializer works so we'll have to see if that can be overridden somehow.
public DbMigrationsConfiguration()
{
this.SetSqlGenerator("System.Data.SqlClient", new SqlServerMigrationSqlGenerator());
this.SetSqlGenerator("System.Data.SqlServerCe.4.0", new SqlCeMigrationSqlGenerator());
this.CodeGenerator = new CSharpMigrationCodeGenerator();
}
Still open to ideas!
Based on some reflected code, it looks like the issue is that there is hardcoded logic for non-System.Data.SqlClient or sqlce providers in DatabaseCreator class that forces the generation down a different path.
public void CreateDatabase(InternalContext internalContext, Func<DbMigrationsConfiguration, DbContext, DbMigrator> createMigrator, ObjectContext objectContext)
{
if (internalContext.CodeFirstModel == null || !(internalContext.ProviderName == "System.Data.SqlClient") && !(internalContext.ProviderName == "System.Data.SqlServerCe.4.0"))
{
internalContext.DatabaseOperations.Create(objectContext);
internalContext.SaveMetadataToDatabase();
}
else
{
Type type = internalContext.Owner.GetType();
DbMigrationsConfiguration dbMigrationsConfiguration = new DbMigrationsConfiguration();
dbMigrationsConfiguration.ContextType = type;
dbMigrationsConfiguration.AutomaticMigrationsEnabled = true;
dbMigrationsConfiguration.MigrationsAssembly = type.Assembly;
dbMigrationsConfiguration.MigrationsNamespace = type.Namespace;
dbMigrationsConfiguration.TargetDatabase = new DbConnectionInfo(internalContext.OriginalConnectionString, internalContext.ProviderName);
createMigrator(dbMigrationsConfiguration, internalContext.Owner).Update();
}
internalContext.MarkDatabaseInitialized();
}
In the end, we updated our datacontext constructor so that the DefaultConnectionFactory is set in code and not in config. In development (debug mode) only, if db doesn't exist, we set first to the SqlConnectionFactory since it generates the db with indexes and better naming that we want. After that or in release mode we want to use the custom provider which has retry logic we want.
I recently upgraded from EF6 alpha 1-3 to EF6 beta 1. This meant that I had to recreate all the migrations created using the alpha version.
So I tried to roll back to a migration created using EF5. But I hit the error Introducing FOREIGN KEY constraint on table may cause cycles or multiple cascade paths. I figure this is because I had neglected to fix Down migrations when I was fixing Up migrations for exactly the same problem. (Should have read this before)
Anyway, rather than try to fix it all up I am trying to reset all the migrations - as described here. I deleted my migrations table in the database and all migration .cs files, then in package manager Enable-Migrations -EnableAutomaticMigrations -Force and Add-Migration Initial
When I tried to run my application with my existing database initialiser (which has automatic migrations false) it failed because it tried to create tables that were already there. So I changed my initialiser to Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>())
This time I got the Introducing FOREIGN KEY constraint on table may cause cycles or multiple cascade paths problem again during initialisation
So I changed ALL the cascadeDelete: true to cascadeDelete: false in the migration file
But I still get the same error!
Update 1 I removed all but the creation of 1 table in the migration file but I got the same error. There must be some kind of cache somewhere or it's picking up a file I don't know about or it's generating its own migration in the background
Update 2 I figured that when using DropCreateDatabaseAlways that EF must always generate the migrations and also that changing cascadeDelete to false in the migration file is the wrong place to do it. It should be done in the FluentAPI. So I added this line modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); to onmodelcreating. And I also deleted the Initial migration file. Then I ran the application and it correctly generated a database. I thought I'd cracked it but....
I changed initialisation to use my original configuration file:
internal sealed class Configuration : DbMigrationsConfiguration<SID2013Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(etc..
}
Then I ran the application and it reported that the model had changed. So I did Add-Migration Update-Database and a migration file to create the database was created.
The problem now is that when I run the application it tries to run another update (even though AutomaticMigrationsEnabled = false). I get the "There is already an object named 'xxx' in the database" problem again. There is an entry in the migrations table that does not match the name of the configuration file.
If you would like to start work "code first migration" using an existing database, you can do the following:
I run the add-migration "InitialMigrations".
It explores the existing database and make it a migration step.
temporarily delete the contents of the "InitialMigrations" step:
public partial class InitialMigrations : DbMigration {
public override void Up()
{
//...
//...
}
public override void Down()
{
//...
//...
}
}
I run the update-database
This creates the appropriate entries in the table __MigrationHistory.
Restores the contents of the "InitialMigration" Go to work properly on an empty database.
That's it.
update: initializer
As I understand it, the 'DropCreateDatabaseAlways' always delete and create the database. This can only be used for the very first installation. If you have a working installation launchpad, you erase all data. That is why I hazardous.
I based on this article: coding.abel.nu/2012/03/… my own Initializer I'm using. I'll write.
The seed is definitely executed, so this method yields the same result, but it works even if you do not run installation, but run upgrade.
public partial class MyEntities : DbContext
{
static MyEntities()
{
Database.SetInitializer<MyEntities>(new ValidateDatabase<MyEntities>());
}
}
/// <summary>
/// http://coding.abel.nu/2012/03/prevent-ef-migrations-from-creating-or-changing-the-database/
/// </summary>
/// <typeparam name="TContext"></typeparam>
public class ValidateDatabase<TContext> : IDatabaseInitializer<TContext>
where TContext : DbContext
{
public void InitializeDatabase(TContext context)
{
if (!context.Database.Exists())
{
throw new ConfigurationErrorsException(
"Database does not exist");
}
else
{
if (!context.Database.CompatibleWithModel(true))
{
//from:
//http://stackoverflow.com/questions/11611322/ef-4-3-code-first-migrations-seed-per-migration
var cfg = new Migrations.Configuration();
var migrator = new DbMigrator(cfg);
///Last in the db, (first, the reverse order)
var dbLast = migrator.GetDatabaseMigrations().FirstOrDefault();
///last in the app
var appLast = migrator.GetLocalMigrations().LastOrDefault();
///what is missing
var pendings = string.Join(", ", migrator.GetPendingMigrations());
throw new InvalidOperationException(
string.Format("The database ({0}) is not compatible with the entity model ({1}). Pending migrations: {2}",
dbLast, appLast, pendings));
}
}
}
}
update: setup
Installation I'm using something like this:
http://coding.abel.nu/2012/04/update-database-msi-custom-action/
I finally fixed this by dropping the database manually then running the application with the original MigrateDatabaseToLatestVersion initializer. I had not realised that this would create the database if it did not exist and there was no need to use a DropCreateDatabaseAlways initializer then change to MigrateDatabaseToLatestVersion
I am using EF 4.1, and I create a normal EF edmx file.
I generate it from a DB.
When it's been generated I rightclick and select add code generation item, to generate new classes, and use the DbContext instead. I use the template DbContext generator.
Everything works fine.
Then I trie to query the context:
using (var context = new PasDBEntities())
{
var client=context.ClientCompanies.SingleOrDefault(_=>_.ID==clientCompanyId);
if(client!=null)
I have no problem creating a new instance of the context but when I try to query it the problem occur. I get stuck on the UnintentionalCodeFirstException.
And gets the error:
{"Code generated using the T4 templates for Database First and Model First development may not work correctly if used in Code First mode. To continue using Database First or Model First ensure that the Entity Framework connection string is specified in the config file of executing application. To use these classes, that were generated from Database First or Model First, with Code First add any additional configuration using attributes or the DbModelBuilder API and then remove the code that throws this exception."}
I don't want to use code first, but I don't know if I can "switch" it off, or where the problem is.
For reference, here is my constructor ...
public partial class PasDBEntities : DbContext
{
public PasDBEntities()
: base("PasDBEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
...and connection string:
<connectionStrings>
<add name="PasDBEntities"
connectionString="metadata=res://*/PasDB.csdl|
res://*/PasDB.ssdl|
res://*/PasDB.msl;
provider=System.Data.SqlClient;
provider connection string="
data source=localhost;
initial catalog=PasDB;
integrated security=True;
pooling=False;
multipleactiveresultsets=True;
App=EntityFramework""
providerName="System.Data.EntityClient" />
</connectionStrings>
I see you are using the EDMX with Templates (.tt) for generating the classes. But if you are getting the information from a existing database, the wizard will create a ConnectionString compatible with ObjectContext (metadata informations and provider of entityframework).
The problem is that the connectionstring you are using is for ObjectContext (Database First and Model First). For the DbContext you should use the connectionstring without the metadata informations.
Your connection string should be:
<connectionStrings>
<add name="PasDBEntities"
connectionString="data source=localhost;
initial catalog=PasDB;
integrated security=True;
pooling=False;
multipleactiveresultsets=True;
App=EntityFramework"
providerName="System.Data.SqlClient" />