entity framework 6.1 Seed & OnModelCreating not working together - c#

I'm using EF in a Web API project with code first mode.
So far, so good, but I have Time field in a table, where I want Precision 0.
So I added the required codes in the OnModelCreating method of the DBContext class.
after that the Seed method in Configuration class was not executed.
when I comment the code in OnModelCreating, the Seed method is executing.
I would be happy if somebody could give me a reason for this, and how shall I restructure my classes to run both of the methods.
Normally I use the standard way. I have the simplest DbContext:
public partial class HarmoniaContext : DbContext {
public HarmoniaContext()
: base("Harmonia") {
}
...virtual properties or the DbSet<>...
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Nyitvatartas>()
.Property(e => e.KezdIdo)
.HasPrecision(0);
modelBuilder.Entity<Nyitvatartas>()
.Property(e => e.BefIdo)
.HasPrecision(0);
}
}
and the configuration file
internal sealed class Configuration :DbMigrationsConfiguration<HarmoniaContext> {
public Configuration() {
AutomaticMigrationsEnabled = true;
}
protected override void Seed(HarmoniaContext context) {
context.Beallitas.AddOrUpdate(b => b.Nev, ...}
context.Szerepkor.AddOrUpdate(sz=> sz.RendszerNev, ...}
}
}

Related

Adding new DbSet to DbContext when application has started and DbContext created

I have a project, in business it will creates table dynamicaly, its working with netcore3.0 and EF.
When an instance of dbcontext is created after dynamic table is created, I will use Assembly Emit to create a new type of the table, and use OnModelCreating method to add dbsets corresponding to tables.
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public virtual DbSet<Book> Books { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
//Use assmbly emit to create dynamic types
var types = CreateDynamicTypes();
foreach (var type in types)
{
builder.Entity(type);
}
base.OnModelCreating(builder);
}
}
But when a table is created after the dbcontext is created, I dont know how to add new dbset yet, because the OnModelCreating only run 1 time.
The question: How do I add new dbsets to an instance of dbcontext after its created?
OnModelCreating run only 1 time (when it first initialized) because of performance overhead.
There is one way, to bypass this, by using "Model Customizer"
First, you need some tweaking in OnConfiguring (you need to override basic implementation)
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
var serviceCollection = new ServiceCollection()
.AddEntityFrameworkSqlServer();
serviceCollection = serviceCollection.AddSingleton<IModelCustomizer, YourModelCustomizer>();
var serviceProvider = serviceCollection.BuildServiceProvider();
optionsBuilder
.UseInternalServiceProvider(serviceProvider);
}
And your Customizer should look like
public class YourModelCustomizer : ModelCustomizer
{
public override void Customize(ModelBuilder modelBuilder, DbContext dbContext)
{
base.Customize(modelBuilder, dbContext);
var entityTypeBuilderCart = modelBuilder.Entity<Models.Cart>()
.ToTable("ABC");
entityTypeBuilderCart.Property(a => a.UserId).HasColumnName("XYZ");
entityTypeBuilderCart.Property(a => a.ContractorId).HasColumnName("DFG");
entityTypeBuilderCart.Ignore(a => a.CompanyId);
var entityTypeBuilderCartArticle = modelBuilder.Entity<Models.CartArticle>()
.ToTable("IJK");
entityTypeBuilderCartArticle.Property(a => a.UserId).HasColumnName("QWE");
}
public YourModelCustomizer(ModelCustomizerDependencies dependencies) : base(dependencies)
{
}
}
I hope it will help you.
Be aware that this kind of configuration may cause performance issue.
This code works in EF Core 2.x, in EF 3.x may be some changes, and this code might need some changes.

EF Core HasQueryFilter works for only the first value in filter expression

I am using EF Core HasQueryFilter extension method, which is inside the OnModelCreating method.
I am injecting the user id into the DbContext using a service and then applying the userId to the query filter. For the first time when the OnModelCreating is executed it works fine as expected. But when I change the user and pass a different userId to the DbContext then query filter is not affected as obvious because the OnModelCreating is not called this time.
A little background of the App: It's a core 2.2 API project which authenticates users using the JWT token. I populate the user claims and initialize the injected auth service using the JWT, so for every call to the API the userId can be different hence query filter should work on different userIds.
Example codes below:
public class SqlContext : DbContext
{
private readonly IAuthService _authService;
public SqlContext(DbContextOptions options, IAuthService authService) : base(options)
{
_authService = authService;
}
public DbSet<Device> Devices { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Device>().HasQueryFilter(p => !p.IsDeleted && p.ManufacturerId == _authService.ManufacturerId);
}
}
How the DbContext is initialized.
services.AddDbContextPool<TContext>(o =>
o.UseSqlServer(configuration["Settings:SqlServer:DefaultConnection"],
b =>
{
b.MigrationsAssembly(configuration["Settings:SqlServer:MigrationAssembly"]);
b.CommandTimeout(60);
b.EnableRetryOnFailure(2);
})
.ConfigureWarnings(warnings =>
{
warnings.Throw(RelationalEventId.QueryClientEvaluationWarning);
}))
.AddTransient<TContext>();
Finally solved it.
As the filter was working, but it was not getting updated once the model was created after first request. The reason was that EF was caching the created model. So, I had to implement the IModelCacheKeyFactory in order to capture the different models as per the filters.
internal class DynamicModelCacheKeyFactory : IModelCacheKeyFactory
{
public object Create(DbContext context)
{
if (context is SqlContext dynamicContext)
{
return (context.GetType(), dynamicContext._roleCategory);
}
return context.GetType();
}
}
And attached it to the context like this.
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
base.OnConfiguring(builder);
builder.ReplaceService<IModelCacheKeyFactory, DynamicModelCacheKeyFactory>();
}

How to properly use IPluralizer in IDesignTimeServices

In my app I use code-first approach and I'm adding entities to DbContext only via IEntityTypeConfiguration<>.
My goal is to achieve pluralized table names i.e. Models for Model.
After reading documentation, article, This question
my understading would be that my pluralizer should be registered and as IPluralizer used during creating migration, however it is not.
Of course I could have implicitly use DbSet<Model> Models {get;set;} or use builder.ToTable("Models");, but in my generic scenario I'd like to avoid that, especially as I would like some models not to override abstract generic scenario.
Question is, am I doing something wrong, or I misunderstand the way it should behave
AppDesignService.cs
public class AppDesignService : IDesignTimeServices
{
public void ConfigureDesignTimeServices(IServiceCollection services)
{
Debugger.Launch();
services.AddSingleton<IPluralizer, InflectorPluralizer>();
}
}
MyDbContext.cs
public class AppDbContext : IdentityDbContext<AppUser,AppRole,Guid>
{
public EmsDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfigurationsFromAssembly(Assembly.GetAssembly(typeof(AppDbContext)));
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<DbContext, AppDbContext>(opt =>
{
opt.UseSqlServer(connectionString, sqlOpt =>
{
sqlOpt.EnableRetryOnFailure(3);
});
});
// code skipped for brevity
}
Config
public interface IEntity
{
int Id {get;set;}
}
public class Model : IEntity
{
public int Id {get;set;}
public string Name {get;set;}
}
public abstract class DbEntityConfig<T> : IEntityTypeConfiguration<T>
where T : class, IEntity
{
public void Configure(EntityTypeBuilder<T> builder)
{
builder.HasKey(m => m.Id);
}
}
public class ModelEntityConfig : DbEntityConfig<Model>
{
public void Configure(EntityTypeBuilder<Model> builder)
{
base.Configure(builder);
// Of course I want to avoid this call, cos TheOtherModel might not override base method
// builder.ToTable("Models");
builder.Property(m => m.Name).HasMaxLength(25);
}
}
Result
public partial class Test : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Model",
columns: table => new
// code skipped for brevity
}
}
Expected result:
public partial class Test : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Models",
columns: table => new
// code skipped for brevity
}
}
The linked article is incorrect. As you can see from Pluralization hook for DbContext Scaffolding EF Core documentation:
EF Core 2.0 introduces a new IPluralizer service that is used to singularize entity type names and pluralize DbSet names.
Shortly, it is used only by scaffolding commands, hence cannot be used for changing the table name model conventions.
In general migration tools use the model the way it is configured by conventions, data annotations and fluent API. So applying custom convention should be with model API inside OnModelCreating. Something like this:
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
entityType.Relational().TableName = GetTableName(entityType);
where GetTableName method implements your naming convention:
string GetTableName(IEntityType entityType)
{
// use entiityType.Name and other info
return ...;
}
Update (EF Core 3.0+): Use entityType.SetTableName(...) in place of entityType.Relational().TableName = ...

EF Code-First from existing db add models into existing DbContext

So maybe this is a stupid question, because I know that when creating a Code-first model from an existing database, Visual Studio will create a new ADO.NET Entity Data Model, and add the models in a new DbContext.
I am using Microsoft Identity in my project, hence there is already a ApplicationDbContext (IdentityDbContext). I think just having all my models in a single DbContext would be easier to handle. I am generating my code-first models from an existing database.
But Is there a way such that the generated models add up into the already existing DbContext (In this case, the IdentityDbContext?)
I have like, many models, so currently I am compelled to add each of them into existing ApplicationDbContext manually, and remove from the created DbContext.
As far as I remember there is no way to add the generated models objects to the existing DbContext automatically. You need to add them manually.
public partial class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("name=DefaultConnection")
{
}
//Add your Model objects here, You can copy them from automatically generated DbContext
public virtual DbSet<ModelObjectName> PropertyName { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Copy the modelBuilder configuration here from automatically generated DbContext here
base.OnModelCreating(modelBuilder);
}
}
Here's an alternative that works:
Mark your ApplicationDbContext class as partial:
public partial class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext() : base("DefaultConnection")
{
//unchanged
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//unchanged
}
}
Do the same with your custom Data model class, and remove the inheritance from DbContext. Also, remove constructor form it and change the OnModelCreating into a regular private method with some different name. Keep the rest unchanged.
public partial class MyDataModels
{
//unchanged
private void OnModelCreating2(DbModelBuilder modelBuilder)
{
//unchanged
}
}
Refactor (Ctrl + R + R : default shortcut) name of your Data model class, and change it to ApplicationDbContext. Visual Studio might give you a conflict warning during refactoring, ignore that and refactor. Finally, call OnModelCreating2() from OnModelCreating() method of ApplicationDbContext class.
public partial class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext() : base("DefaultConnection")
{
//unchanged
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//unchanged
OnModelCreating2(modelBuilder);
}
}
Another nice approach suggested by #DevilSuichiro is to simply inherit your Data model class from ApplicationDbContext.
Cheers!

Seed method not called, Entity Framework 6

I have a DatabaseInitializer class
public class DatabaseInitializer : CreateDatabaseIfNotExists<DatabaseContext>
{
protected override void Seed
(
DatabaseContext databaseContext
)
{
// Seed the hash methods.
var defaultHashMethod = new HashMethod
{
Description = "Default",
CreateDate = DateTime.Now
};
databaseContext.HashMethod.Add(defaultHashMethod);
databaseContext.SaveChanges();
}
}
In my DatabaseContext class I set the initializer
public DatabaseContext() : base("DatabaseContext")
{
InitializeDatabase();
}
private void InitializeDatabase()
{
Database.SetInitializer(new DatabaseInitializer());
if (!Database.Exists())
{
Database.Initialize(true);
}
}
As far as I can understand the seed method is only invoked once you perform an operation such as a query. My database is created successfully and I'm querying the table, but the seed method is never called.
Update:
It seems like the problem is caused because of a class that is inheriting from my DatabaseContext class, when using this class to perform database operations, the seed method is not called. When using my DatabaseContext class, everything works as expected
public DbSet<TestEntity> TestEntity { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
You need to call Update-Database from the Package Manager Console.
The only way I could get this to work was to call the seed method myself
Here are the methods for my DatabaseContext class
public DatabaseContext() : base("DatabaseContext")
{
InitializeDatabase();
}
public DatabaseContext(string connectionString) : base(connectionString)
{
Database.Connection.ConnectionString = connectionString;
InitializeDatabase();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
Here I changed my InitializeDatabase method from
private void InitializeDatabase()
{
Database.SetInitializer(new DatabaseInitializer());
if (!Database.Exists())
{
Database.Initialize(true);
}
}
to
protected virtual void InitializeDatabase()
{
if (!Database.Exists())
{
Database.Initialize(true);
new DatabaseInitializer().Seed(this);
}
}
This can happen if your Update-Database command does not run successfully, and this does not necessarily mean that it errors out. There might be changes that EF recognizes as "outstanding" that need to be added to a migration.
Try calling "Add-Migration {migrationNameHere}" and then try "Update-Database" again.
to get Seed method to be called when you are not using AutomaticMigration, you should use MigrateDatabaseToLatestVersion initializer for your code-first database.
like this:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<YourContext,YourConfiguration>());
this way, Seed method will be called every time the migration is done successfully.
I had this issue and the problem was my Context constructor did not use the same name as in my web.config.
If you are using Code-First then you can populate the data when the application runs for the first time.
Create a DbInitializer
public class MyDbInitializer : IDatabaseInitializer<MyDbContext>
{
public void InitializeDatabase(MyDbContext context)
{
if (context.Database.Exists())
{
if (!context.Database.CompatibleWithModel(true))
{
context.Database.Delete();
}
}
context.Database.Create();
User myUser = new User()
{
Email = "a#b.com",
Password = "secure-password"
};
context.Users.AddOrUpdate<User>(p => p.Email, myUser);
context.SaveChanges();
}
}
Register this DbInitializer in your Global.asax.cs Application_Start method
Database.SetInitializer(new My.namespace.MyDbInitializer());
My seed was not being executed either. However, it was because I added a column to a model that I had no intention of using in my actual database and forgot to use the [NotMapped] annotation.
[NotMapped]
public string Pair { get; set; }
There was no error message relating to this being the cause at all. Just a null reference to my repository obj when I tried to query data that should have been there.

Categories

Resources