I'm using doing database first development and using Scaffold-DbContext to create my entity models project. In that project, I have a partial dbContext where I override methods like SaveChangesAsync to set certain properties like, "LastModifiedBy".
I'm looking to soft-delete records using dateDeleted/userDeleted columns. When I go to override the OnModelCreating routine, I see that it's already defined on the auto-generated partial context.
I'm trying to do something like the following:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<SomeEntity>().HasQueryFilter(x => x.DateDeleted == null);
}
Does anyone have any suggestion as to how I can do this in a partial class way that still allows me to regenerate the original context on the fly using Scaffold-DbContext?
Also note, I'm using .NET Core 2.1.5
ANSWER FROM David Browne - Microsoft
Add a static property on the partial context
public static bool GlobalFiltersAdded { get; set; } = false;
Then add a routine to add your filters:
private void AddGlobalFilters(ModelBuilder modelBuilder){
SomeContext.GlobalFiltersAdded = true;
}
Then in your generated context add the following:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
AddGlobalFilters(modelBuilder);
//OTHER LOGIC WILL BE HERE
}
Options:
1) Delete the generated OnModelCreating after re-scaffolding. It's already a manual process, and generates a compile error if you forget.
2) Use a DbContext inherited from the generated DbContext.
3) Use a 3rd party tool or library like: EF Core Power Tools
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 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.
In Entity Framework by using Enable-Migrations a Migrations folder is created containing a Configuration inherited from DbMigrationsConfiguration like this:
internal sealed class Configuration : DbMigrationsConfiguration<MyDbContext>
{
...
}
All the created migrations which are created using Add-Migration are placed in the Migrations folder too.
public partial class Init: DbMigration
{
public override void Up()
{
...
}
public override void Down()
{
...
}
}
I didn't find any code that relates these two together ( for example having a configuration property in migrations). The only relation I found is that both are placed in same folder. If I have more than 1 DbContext and consequently more than 1 Configuration, I'm wondering how these DbMigrations are distinguished?
Question: How DbMigration classes are related to a Configuration?
They are related by convention. By default, it will store the migrations in a root folder called Migrations. You can override this in the constructor of the config (https://msdn.microsoft.com/en-us/library/system.data.entity.migrations.dbmigrationsconfiguration(v=vs.113).aspx) or when you enable-migrations:
public Configuration()
{
AutomaticMigrationsEnabled = true;
MigrationsDirectory = #"Migrations\Context1";
}
For multiple contexts, create a different config and folder for each by using -ContextTypeName ProjectName.Models.Context2 -MigrationsDirectory:Migrations\Context2. Here is a walkthrough: http://www.dotnettricks.com/learn/entityframework/entity-framework-6-code-first-migrations-with-multiple-data-contexts
When you run the update-database command, the database operations in the up() method in the latest DbMigration derived classes is performed. If that is successful, the commands in the Configuration class are executed. One of those methods is the seed() method where you can optionally add code to plug values into your tables after a migration. When you specify a target migration (presumably earlier than the latest), the migration works through the chain of down() methods in the migration classes to get to the version you wanted.
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?