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?
Related
Similar questions have been posted before (1, 2, 3), but the situation is different.
I have an .Net Core 7 app using EFCore with some existing entities. Now that everything in there is tested and working, I'd like to add the Identity functionality and then link AspNetUsers to a couple of my existing entities.
Currently there are no identity tables, nor any sort of tables used by the Identity functionality.
Using the Microsoft tutorial Scaffold Identity into a Razor project without existing authorization I get to the step whereby I need to specify my database and user contexts.
After unsuccessfully looking for documentation I entered the database context as being the one which is used in my data project ('EventFinderData' below - a different project to my web app, but within the same solution). I then created a new user context...
After the scaffolding is complete, I get a number of ambiguous reference errors as the scaffolding process creates a new data context:
// This is a duplicate class of that within the EventFinderData project, which is already referenced within my web app
public class EventFinderContext : IdentityDbContext<EventFinderWebAppRazorUser>
{
public EventFinderContext(DbContextOptions<EventFinderContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
I tried to make both classes partial, but then when I add a migration I get:
More than one DbContext was found. Specify which one to use. Use the
'-Context' parameter for PowerShell commands and the '--context'
parameter for dotnet commands.
Some experts seem to recommend starting a new project with Identity included already, and building out from there, but its going to be a big job.
What do I need to modify please in order for this scaffolding process to bolt into my existing EFCore logic?
Alternatively is Identity supposed to use a dedicated (separate) context? That would seem unintuitive given the tables need relationships between identity entities and my existing entities.
Update
Based on suggestions, here are the three classes I get after adding the Identity functionality:
Data project (containing Entities and migrations):
EventFinderContext.cs
namespace EventFinderData
{
public class EventFinderContext : DbContext
{
public DbSet<EventItem> EventItems { get; set; }
public DbSet<EventCategory> EventCategories { get; set; }
public EventFinderContext(DbContextOptions<EventFinderContext> options) : base(options)
{
}
}
}
Web app project - Classes created by VS/Identity
Areas\Identity\Data\EventFinderWebAppRazorContext.cs
namespace EventFinderWebAppRazor.Data;
public class EventFinderWebAppRazorContext : IdentityDbContext<EventFinderWebAppRazorUser>
{
public EventFinderWebAppRazorContext(DbContextOptions<EventFinderWebAppRazorContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
Areas\Identity\Data\EventFinderWebAppRazorUser.cs
namespace EventFinderWebAppRazor.Areas.Identity.Data;
public class EventFinderWebAppRazorUser : IdentityUser
{
}
Program.cs
builder.Services.AddDbContextFactory<EventFinderContext>(options =>
options.UseSqlServer(
builder.Configuration.GetConnectionString("EventFinderConnString"),
ss => ss.UseNetTopologySuite())
.EnableSensitiveDataLogging());
builder.Services.AddDefaultIdentity<EventFinderWebAppRazorUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<EventFinderWebAppRazorContext>();
Not sure if something changed (.net6 here) but to add the dot net identity framework to your existing context all you have to do is just provide that context when adding it to the IServiceCollection.
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
})
.AddEntityFrameworkStores<ApplicationDbContext>()
You will need to change your DbContext so it inherits from
IdentityDbContext<ApplicationUser>
This will only provide the required services, not the different views and UX flows that comes when you scaffold it. So no login/logout, password resets, MFA setup etc...
I hope this will help.
Update
Based on the OP's update, the following updates are required.
Areas\Identity\Data\EventFinderWebAppRazorUser.cs
Move this class to your Data project. It will become a part of your own entity model. Make sure that the namespace is also updated. Although this is not really necessary, but it is nice to have all entities in the same namespace.
Areas\Identity\Data\EventFinderWebAppRazorContext.cs
Delete this file/class. This is the extra context created by the scaffolding process, but as we want to use our own context we don't need it.
EventFinderContext.cs
Update this class like below.
namespace EventFinderData
{
public class EventFinderContext : IdentityDbContext<EventFinderWebAppRazorUser>
{
public DbSet<EventItem> EventItems { get; set; }
public DbSet<EventCategory> EventCategories { get; set; }
public EventFinderContext(DbContextOptions<EventFinderContext> options) : base(options)
{
}
}
}
Program.cs
Update this file like below. And make sure all types resolve by updating the usings.
builder.Services.AddDbContextFactory<EventFinderContext>(options =>
options.UseSqlServer(
builder.Configuration.GetConnectionString("EventFinderConnString"),
ss => ss.UseNetTopologySuite())
.EnableSensitiveDataLogging());
builder.Services.AddDefaultIdentity<EventFinderWebAppRazorUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<EventFinderData.EventFinderContext>();
I've recently added a migration to my data model where I added a bool property to the Member model:
public class Member
{
public bool IsLegacy { get; set; }
}
Having added the migration via dotnet ef migrations add <name>, the migration generated code to create every table.
As a result, when I push the migration to the database dotnet ef database update, I get an error saying
table '...' already exists
The existing table could change from time to time.
I would expect the migration to simply add the column to the table so that the only could would probably be something like:
public partial class AddedLegacyFieldToMember : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsLegacy",
table: "Member",
type: "bit" // assuming sql type is what it needs.
);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn("IsLegacy", "Members");
}
}
So what I can't figure out is why the migration is trying to build every table all new as though no other migration exists.
The migration history table in the database does have the previous migration listed there though.
Have I done something wrong?
Note
I've tried using .EnsureCreated() to sidestep the issue, but clearly that's misguided as it didn't make any difference.
Also, building the migration logic into the Startup doesn't address the problem either.
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.
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 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());