ef core new dbcontext executes OnModelCreating only once - c#

we have regularly changing Database Data(every two weeks to once a month).
Usually the latest data has to be used, but in some special cases older data is necessary.
The current info which version has to be used atm is stored in another table.
The Database looks like this, versioned Schema-Names with the same tables beneath it.
YYYYMMDD+Revision
myshema_202001011
table1
myshema_202002011 and so on
table1
myshema_202003011 and so on
table1
I have build a Aspnet core (2.2) service with two DbContext classes,
one for the static schemas that gets the current version to use and one for the changing schemas that accesses those data.
The static DbContext works just fine.
The problem is, even when i use the changing contaxt with a using like,
using (var _context = new ChangingDbContext()){}
the constructors and OnConfiguring are executed each time but the OnModelCreating method is only executed once.
This leads to NOT updating to the current schemas.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
modelBuilder.Entity<my_table>(entity =>
{
entity.HasKey(e => e.key_adr);
entity.ToTable("mytable", $"myshema{mySchemaVersion}");
});
}
Has anyone a clue how to get a "really" new context where OnModelCreating is executed every time?
Or maybe another solution how to handle those changing Schemas?

To continue from my comment. The below db table design allows you or users add as many as new fields to an object as they want. And I think it gives most flexible structure.
Let's assume in a eCommerce system, we provide 3 fields (Name, Code, Price) for the product. But we also allow users want to add their custom fields to their products (e.g. Promotion1Price, Promotion2Price, Discount, ...)
PRODUCT (ProductId, Name, Code, Price)
CUSTOMEFIELD (FieldId, FieldName, FieldType)
PRODUCT_CUSTOMFIELD (ProductId, FieldId, FieldValue)
Let me know if this doesn't serve your purpose right.

Solved by this Answer https://stackoverflow.com/a/41985226/6692289
Quote from Example in case it gets deleted.
Derived DbContext that replaces it's ModelCacheKey (and factory) with
a Custom one.
class MyDbContext : DbContext
{
public MyDbContext(string schema)
{
Schema = schema;
}
public string Schema { get; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options
.UseSqlServer("...")
.ReplaceService<IModelCacheKeyFactory, MyModelCacheKeyFactory>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema(Schema);
// ...
}
}
The factory that creates the Context with a specific key.
class MyModelCacheKeyFactory : IModelCacheKeyFactory
{
public object Create(DbContext context)
=> new MyModelCacheKey(context);
}
The custom ModelCacheKey per context.
class MyModelCacheKey : ModelCacheKey
{
string _schema;
public MyModelCacheKey(DbContext context)
: base(context)
{
_schema = (context as MyDbContext)?.Schema;
}
protected override bool Equals(ModelCacheKey other)
=> base.Equals(other)
&& (other as MyModelCacheKey)?._schema == _schema;
public override int GetHashCode()
{
var hashCode = base.GetHashCode() * 397;
if (_schema != null)
{
hashCode ^= _schema.GetHashCode();
}
return hashCode;
}
}
And using the Context like.
using (var _myContext = new MyDbContext(_schemaNameToUse)
{
}

Related

Audit.Net on Update inserts empty records for every value that is in the table

I am using Audit.Net EntityFramework data provider.
This is my configuration in Startup.cs
Audit.Core.Configuration.Setup().UseEntityFramework(_ => _.AuditTypeMapper(t => typeof(AuditLog)).AuditEntityAction<AuditLog>((ev, entry, entity) =>
{
entity.AuditData = entry.ToJson();
entity.AuditDate = DateTime.Now;
entity.AuditUser = Environment.UserName;
// entity.AuditUsername = Environment.MachineName;
entity.AuditUsername = HttpContext.Current.User.Identity.Name;
})
.IgnoreMatchedProperties(true));
This is DBContext
public class DbContext : Audit.EntityFramework.AuditDbContext, IDbContext
{
public DbContext()
: base("DbContext")
{
Database.SetInitializer<DbContext>(null);
}
public Database GetDatabase()
{
return this.Database;
}
public new DbEntityEntry<T> Entry<T>(T entity) where T : class
{
return (new PContext().Entry(entity) as DbEntityEntry<T>);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
The problem only occurs when I change the value on a form or data table
The highlighted is the updated audit rec. All below are added and it seems it enters some kind of recursion since the first JSON record after the Update rec has 1281 characters, second 1146 , third 1763 ,4-th 2773 and so on until I stop the project.
at entity.AuditData = entry.ToJson(); getting this
System.OutOfMemoryException: 'Exception of type 'System.OutOfMemoryException' was thrown.'
What might be the problem?
If you are making changes to your AuditLog DbSet directly from your audited DbContext, you should mark the AuditLog entity as not auditable so the changes are not picked up by the audit library.
This can be done by decorating the class with [AuditIgnore] for example:
[AuditIgnore]
public class AuditLog
{
...
}
Or via configuration:
Audit.EntityFramework.Configuration.Setup()
.ForContext<P121DbContext>()
.UseOptOut()
.Ignore<AuditLog>();

ORM and prefixes for the tables

Task this, when registering the customer in the database we need to create a set of tables with a unique prefix, it is generally possible to implement using EF?
I haven't tested this but it should work, or at least be a good start point. First you need to tell your context the prefix you will be using, I'd do this in the constructor. Then in the OnModelCreating override, use the prefix for the table names:
public class MyContext : DbContext
{
private readonly string _prefix;
public MyContext(string prefix)
{
_prefix = prefix;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Types()
.Configure(entity => entity.ToTable(_prefix + entity.ClrType.Name));
}
}

Emit DbContext.OnModelCreating on every context creation

I use entity framework code first to work with my database.
I have several tables with different names but same structure, and this tables dynamically appears in database. How could I map EntityFramework to one of that tables at run-time and use data from just like I work this over entities of DbContext?
What I've done to make it work:
For example, my class what describes structure of dynamically created table is SetElement.
Here is my context:
public class DataContext : DbContext
{
public DataContext()
: base("RepositoryConnectionString") { }
string setElementsTableId; // the name of table that need to be dynamicly mapped to
// Enforce model recreating
public DataContext(string setElementsTableId)
: this()
{
this.setElementsTableId = setElementsTableId;
}
/* some other entities */
public DbSet<Entities.SetElement> SetElements { get; set; } // dynamicly mapped entity
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
/* come configurations */
if (!string.IsNullOrEmpty(setElementsTableId))
{
modelBuilder.Entity<Entities.SetElement>().Map(x => x.ToTable(setElementsTableId)); // map SetElements property to dynamicly created table
}
}
}
How I use this:
public static void AddSetElements(ICollection<SetElement> setElements, string tableId)
{
using (ctx = new DataContext(tableId)) // configere DataContext to map tableId table for entity SetElements
try
{
var num = ctx.SetElements.Count();
ctx.SetElements.AddRange(setElements);
ctx.SaveChanges();
}
catch (Exception e)
{
}
}
I have also some methods to get, udtate and remove data from dynamicly created tables that are same to AddSetElements.
All works just as I wish but only if AddSetElements runs first, because at the first datacontext creating DbContext.OnModelCreating runs and configure all mappings. But next instance creation doesn't call DbContext.OnModelCreating.
So, my question is: how to call DbContext.OnModelCreating everytime of creating an instance of DataContext then I use DataContext(string setElementsTableId) to create it?
I know, my question is similar to 'dynamic table mapping in EF' but I found nothing in the results.
By the way. If you know another way to solve my problem, you are welcome.
There is a built-in feature which may address your issue : `IDbModelCacheKey ; the implementation of which is to be registered in your configuration.
The point is to generate a different key for your different contexts.
I would go for something like :
First, the configuration
public class EntityFrameworkConfiguration: DbConfiguration
{
public EntityFrameworkConfiguration()
{
this.SetModelCacheKey(ctx => new EntityModelCacheKey((ctx.GetType().FullName + ctx.Database.Connection.ConnectionString).GetHashCode()));
}
}
Then the implementation of the IDbModelCacheKey
public class EntityModelCacheKey : IDbModelCacheKey
{
private readonly int _hashCode;
public EntityModelCacheKey(int hashCode)
{
_hashCode = hashCode;
}
public override bool Equals(object other)
{
if (other == null) return false;
return other.GetHashCode() == _hashCode;
}
public override int GetHashCode()
{
return _hashCode;
}
}
Finally, your DataContext
public class DataContext : DbContext
{
string setElementsTableId;
// use the setElementsTableId as extended property of the
// connection string to generate a custom key
public DataContext(string setElementsTableId)
: base(ConfigurationManager.ConnectionStrings["RepositoryConnectionString"]
+ "; Extended Properties=\"setElementsTableId=" + setElementsTableId + "\"")
{
this.setElementsTableId = setElementsTableId;
}
public DbSet<Entities.SetElement> SetElements { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
if (!string.IsNullOrEmpty(setElementsTableId))
{
modelBuilder.Entity<Entities.SetElement>().Map(x => x.ToTable(setElementsTableId));
}
}
}
I hope this will be of some help
Look like nobody knows answer...
Otherwise, one man told me that my question is meaningless because of storage data in several tables will not give any achievement. More better to add indexes to database, partitioning table or something else. In other words this is Database Management System problem. But if some one knows answer I'll be very pleasured to hear something about EF hack.

Converting the column type in No Magic Migration

I have an entity which contains a property of type TimeSpan. We have decided to changed the property from TimeSpan to Int (minutes).
My Idea now to migrate this changes as following:
Retrieving each entry from the old column(TimeSpan) and convert it to minute (Int) and add it in the new created column.
Where/what is the best approach to do that? Shall I do it in the Seed method?! I think, this is not possible because Seed is called after the migration is done.
The second approach in Up() method. Is this a good idea? Copy/Convert the column data into another column in the Up method.
public partial class V30 : DbMigration
{
public override void Up()
{
AddColumn("dbo.Plexes", "ISingleIsValidTimeInMinutes", c => c.Int(nullable: false));
// I NEED HERE TO TRANSFER THE DATA FROM ISingleIsValidTime TO ISingleIsValidTimeInMinutes ?!!!
TransformISingleIsValidTimeToInt();
DropColumn("dbo.Plexes", "ISingleIsValidTime");
}
....
}
private void TransformISingleIsValidTimeToInt()
{
// HERE CONVERTING LOGIC I NEED THE EXISITNG DATA FROM DBCONTEXT!!
}
public class NoMagicDatabaseConfiguration : DbMigrationsConfiguration<ApplicationDbContext>
{
public NoMagicDatabaseConfiguration()
{
this.AutomaticMigrationsEnabled = false;
this.AutomaticMigrationDataLossAllowed = false;
}
protected override void Seed(ApplicationDbContext context)
{
base.Seed(context);
}
}
The easiest (and probably quickest) way would be to run a SQL statement with the Sql method, something along the lines of :
public override void Up()
{
AddColumn("dbo.Plexes", "ISingleIsValidTimeInMinutes", c => c.Int(nullable: false));
Sql("UPDATE Plexes SET ISingleIsValidTimeInMinutes = -DATEDIFF(MINUTE, ISingleIsValidTime, 0)");
DropColumn("dbo.Plexes", "ISingleIsValidTime");
}
Alternatively, if you want to solve this problem using EF alone, you could instantiate your context during the up migration between adding and removing the columns and do the work there, but if you have a lot of rows, it could take a while

Change entity map to another "unknown" table at runtime

I have to write a C# application that works with a SQL server database created and mantained by an old application. The application creates new tables each year and the "year property" is in the table name. The number of tables it creates may vary depending of the number of "sections" that the user has created inside the application. So, I have to work with tables like Cwx_DRyz (quite self explanatory...), where "wx" can be the section, and "yz" would be the year. An example of group of table could be:
C01_DR07
C01_DR08
C01_DR09
C02_DR08
C02_DR09
C03_DR06
C04_DR12
And all of those tables could represent, for example, clients. They would be clients from different sections and years, but clients with the same structure.
My question is: Can I have a Client entity to handle all those tables and change the mapping from one to another at runtime? The title says "unknown" because I don't know the tables before runtime.
The most similar question I have found is Entity Framework map multiple tables to one entity and the answer is to use the "Table Per Concrete Type Inheritance", but it is not useful for my case.
PS: EF version 4.3.1 and VS2010
EDIT: The tables don't have primary keys... Most of them have columns that are supossed to have unique values (integer or string).
If you use "code first" you could create a mapping as you want. This also works with existing databases when the mapping you have created match the database.
So whenever you create a context you can build the string (tablename) you want to map to.
Some codesamples for "code first" and how you could start:
The DbContext:
public DbSet<YourEntity> YourEntities { get; set; }
...
// this is called when the db gets created and does the configuration for you => maybe not needed in your case
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
ConfigurationRegistrar configurationRegistrar = modelBuilder.Configurations;
new GeneralEntitiesConfiguration(configurationRegistrar);
}
GeneralEntitiesConfiguration is a class im using to handle the configurations, nothing more than a helper which looks like:
public class GeneralEntitiesConfiguration
{
public GeneralEntitiesConfiguration(ConfigurationRegistrar configurationRegistrar)
{
configurationRegistrar.Add(new YourEntityConfiguration());
//and additional configurations for each entity, just to splitt it a bit and have it more read and maintenance able
}
}
YourEntityConfiguration is a class where i have all the configurations for this entity:
public class YourEntityConfiguration : EntityTypeConfiguration<YourEntity>
{
public YourEntityConfiguration ()
{
ToTable("WhatEverYouLike"); // here you can do any magic to map this entity to a table, just make sure that your properties are mapped to the correct colums
Property(entity => entity.Id).HasColumnName("YouColumnName");
//and here you also have to do the other configurations
}
}
At the application startup (or before you initialize your context the first time) you have to initialize the database. Therefore you can use an initializer which checks the database and handles differences. Build in there are things like "DropCreateDatabaseAlways" or "DropCreateDatabaseIfModelChanges" => you would need to create your own which just ignores any differences. In my sample i have create one which just throws an exception when the model differs (i wanted to handle model changes with scipts for the first try):
//before using the context the first time i'm calling, you can ignore the connection string
DbContextInitializer.Init(conString);
public static class DbContextInitializer
{
public static void Init (string connectionString)
{
Database.SetInitializer(new CreateDbThrowExceptionIfModelDiffersInitializer<SMDbContext>());
using(var dbContenxt = new MyDbContext(connectionString))
{
try
{
dbContenxt.Database.Initialize(true);
}
catch(DatabaseModelDiffersException diffException)
{
// some magic...
}
catch(Exception ex)
{
// TODO: log
throw;
}
}
}
public class CreateDbThrowExceptionIfModelDiffersInitializer<TContext> : IDatabaseInitializer<TContext> where TContext : DbContext
{
public void InitializeDatabase(TContext context)
{
using (new TransactionScope(TransactionScopeOption.Suppress))
{
if (!context.Database.Exists())
context.Database.Create();
}
if (!context.Database.CompatibleWithModel(true))
{
throw new DatabaseModelDiffersException("Database Model differs!");
}
}
protected virtual void Seed(TContext context)
{
// create data if you like
}
}
// just an exception i'm using for later useage
public class DatabaseModelDiffersException : Exception
{
public DatabaseModelDiffersException(string msg) : base(msg)
{}
}
}
Hope you have got an idea of you can handle dynamic table names with entity framework!
If there are more questions just ask ;)

Categories