I am trying to use FluentMigrator with PostgreSQL.
I have it running the migrations successfully, however the VersionInfo table is always in the public schema. I read on the FluentMigrator Wiki that I could override the schema name, but it is not working.
Here is my class that I wrote to override the settings:
namespace YARA.Migrations
{
using FluentMigrator.VersionTableInfo;
[VersionTableMetaData]
public class YaraVersionTable : DefaultVersionTableMetaData
{
public override string SchemaName
{
get { return "dbo"; }
}
public override string TableName
{
get
{
return "MigrationInfo";
}
}
}
}
And here is a screenshot of the db after running the migrations; with neither the schema or changing the table name taking effect for the VersionInfo table.
Thoughts?
You have to create your class, in my case:
[VersionTableMetaData]
public class CustomMetadataTable : DefaultVersionTableMetaData
{
public override string TableName => "__migrations_metadata";
}
Then add it to your services
var serviceProvider = new ServiceCollection()
.UseStandardConfiguration()
.AddFluentMigratorCore()
.ConfigureRunner(rb => rb
.AddPostgres92()
.WithGlobalConnectionString(c => c.GetService<IConfiguration>().GetConnectionString("THECONNECTION_STRING"))
.ScanIn(THEASSEMBLY).For.Migrations())
.AddLogging(lb => lb.AddFluentMigratorConsole())
.AddTransient<IVersionTableMetaData, CustomMetadataTable>()
.BuildServiceProvider(false);
Just make sure is the last thing you register (just right above the last line)
Related
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)
{
}
I want to use annotations for setting the default value for my properties in Entity Framework Core. The issue is that the database is not setting the default values so the value is not being passed down to the database layer.
I want to do something similar to modelBuilder's HasDefaultValueSql:
[DefaultValue("400")]
public int LengthInMeters {get; set;}
How do you convert the below code to attributes?
modelBuilder.Entity<Patient>().Property(c => c.LengthInMeters).HasDefaultValueSql("400");
Using default values by themselves doesn't work. I want to use attributes alone without having to mess with the migrations.
Problems: I've tried other methods with EF but Entity Framework Core doesn't have some items. Such as modelBuilder.Conventions nor AttributeToColumnAnnotationConvention nor CSharpMigrationCodeGenerator nor modelBuilder.Properties()
This is what I ended up doing, if someone has a cleaner not as intensive way of implementation let me know.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
var memberInfo = property.PropertyInfo ?? (MemberInfo)property.FieldInfo;
if (memberInfo == null) continue;
var defaultValue = Attribute.GetCustomAttribute(memberInfo, typeof(DefaultValueAttribute)) as DefaultValueAttribute;
if (defaultValue == null) continue;
property.SqlServer().DefaultValue = defaultValue.Value;
}
}
}
I can set the default value in the database using the default value attribute
[DefaultValue("400")]
public int LengthInMeters {get; set;}
Struggled a while getting this job done in another way using EF-Core conventions. I discovered a way to add so called "Plugins" which implement the IConventionSetPlugin interface with which you can add custom conventions. It needs some additional code to get EntityFramework to use the plugin.
But first things first, let's create our PropertyAttributeConvention.
public class DefaultValueAttributeConvention : PropertyAttributeConventionBase<DefaultValueAttribute>
{
public DefaultValueAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) : base(dependencies) { }
protected override void ProcessPropertyAdded(IConventionPropertyBuilder propertyBuilder, DefaultValueAttribute attribute,
MemberInfo clrMember, IConventionContext context)
{
propertyBuilder.HasDefaultValue(attribute.Value, fromDataAnnotation: true);
}
}
Here we just say the ef propertybuilder to use the default value defined in our [DefaultValue] attribute.
To add the convention we need to create a custom plugin class:
public class CustomConventionSetPlugin : IConventionSetPlugin
{
public ConventionSet ModifyConventions(ConventionSet conventionSet)
{
conventionSet.PropertyAddedConventions.Add(new DefaultValueAttributeConvention(null));
return conventionSet;
}
}
For our plugin to get used, we have to create an ef extension class (which itself contains another ExtensionInfo class)
public class CustomDbContextOptionsExtension : IDbContextOptionsExtension
{
public void ApplyServices(IServiceCollection services)
{
services.AddSingleton<IConventionSetPlugin, CustomConventionSetPlugin>();
}
public void Validate(IDbContextOptions options) { }
public DbContextOptionsExtensionInfo Info => new CustomDbContextOptionsExtensionInfo(this);
private class CustomDbContextOptionsExtensionInfo : DbContextOptionsExtensionInfo
{
public CustomDbContextOptionsExtensionInfo(IDbContextOptionsExtension extension) : base(extension) { }
public override long GetServiceProviderHashCode() => 0;
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo) { }
public override bool IsDatabaseProvider => false;
public override string LogFragment => "";
}
}
In the extension class we're adding our plugin class to the EF-ServiceCollection.
The last step is to go to our DbContext and add our extension. This can be done in the OnConfigure method:
public class MyDatacontext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(new CustomDbContextOptionsExtension());
}
}
Now the [DefaultValue] attribute can be used on our entity properties.
If we want to add different custom conventions we dont have to create all that extension/plugin classes again. Just create a new convention class and add it through our existing plugin class to the convetionSet.
Install Microsoft.EntityFrameworkCore.Relational package, it should solve most of your migration issues when moving to EF core.
Using Entity Framework Core, is there a way to create the table if it does not yet exist? Exception will throw even if EnsureCreated is called in the context:
DbSet<Ticker> Ticker { get; set }
Database.EnsureCreated();
Ticker.Add(...);
dbctx.SaveChanges(); <== exception
Results in exception:
System.Data.SqlClient.SqlException: Invalid object name 'Ticker'
Is there a way to create the table Ticker before data is inserted?
== EDIT==
This questions is not to create/migrate the entire database, the database always exist and most of its tables also exists, but some of the tables may not. So I just need create one or two tables in runtime.
In Entity framework Core (on version 2.2.4) you can use the following code in your DbContext to create tables in your database if they don't exist:
try
{
var databaseCreator = (Database.GetService<IDatabaseCreator>() as RelationalDatabaseCreator);
databaseCreator.CreateTables();
}
catch (System.Data.SqlClient.SqlException)
{
//A SqlException will be thrown if tables already exist. So simply ignore it.
}
Database.EnsureCreated() doesn't create the schema (so your tables) when the database already exists. That's the reason why you get that exception.
You can check that method's documentation.
PS: Make sure you catch the right exception if it changes in the new versions of Entity framework Core.
My guess is that your context is wrongly defined. Maybe you forgot to add the DbSet to your context implementation?
Below code is working perfectly, and I honestly prefer to EnsureCreated() in the constructor of the actual DBContext implementation.
internal class AZSQLDbContext : DbContext
{
public AZSQLDbContext() {
this.Database.EnsureCreated();
}
internal DbSet<TaskExecutionInformation> TaskExecutionInformation { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var dbUser = "your-user";
var dbPW = "your-pw";
optionsBuilder.UseSqlServer(
$"Server=tcp:sample-sql.database.windows.net,1433;Initial Catalog=sample-db;Persist Security Info=False;User ID={dbUser};Password={dbPW};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;");
}
}
TaskExecutionInformation is just a PoCo and could be anything. See below though if you need a bit of guidance.
public class TaskExecutionInformation
{
public Guid Id { get; set; }
public string Status { get; set; }
public int Duration { get; set; }
}
In my case there was 2 applications using same database and those could create its own code-first tables, if they were missing.
So my solution for that is following extension method used in startup on dbcontext:
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Extensions
{
internal static class DbContextExtensions
{
internal static void EnsureCreatingMissingTables<TDbContext>(this TDbContext dbContext) where TDbContext : DbContext
{
var type = typeof(TDbContext);
var dbSetType = typeof(DbSet<>);
var dbPropertyNames = type.GetProperties().Where(p => p.PropertyType.Name == dbSetType.Name)
.Select(p => p.Name).ToArray();
foreach (var entityName in dbPropertyNames)
{
CheckTableExistsAndCreateIfMissing(dbContext, entityName);
}
}
private static void CheckTableExistsAndCreateIfMissing(DbContext dbContext, string entityName)
{
var defaultSchema = dbContext.Model.GetDefaultSchema();
var tableName = string.IsNullOrWhiteSpace(defaultSchema) ? $"[{entityName}]" : $"[{defaultSchema}].[{entityName}]";
try
{
_ = dbContext.Database.ExecuteSqlRaw($"SELECT TOP(1) * FROM {tableName}"); //Throws on missing table
}
catch (Exception)
{
var scriptStart = $"CREATE TABLE {tableName}";
const string scriptEnd = "GO";
var script = dbContext.Database.GenerateCreateScript();
var tableScript = script.Split(scriptStart).Last().Split(scriptEnd);
var first = $"{scriptStart} {tableScript.First()}";
dbContext.Database.ExecuteSqlRaw(first);
Log.Information($"Database table: '{tableName}' was created.");
}
}
}
}
You have a few options here. The simplest is to do:
MyContext.Database.CreateIfNotExists();
Or, do it initialization style, by putting this in your context's constructor:
Database.SetInitializer<MyContext>(new CreateDatabaseIfNotExists<MyContext>());
Both of these however require you to drop your schema manually every time you have modified your model and need to re-create the database. If you don't want to do that, you can use the following initialization instead:
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyContext>());
This will check your model against the database every time you run your program, and automatically drop and re-create the database if the model has been modified.
EDIT:
If you don't want to drop the database, and simply update it, then you can use the following initialization:
Database.SetInitializer<MyContext>(new MigrateDatabaseToLatestVersion<MyContext, Config>());
I am using entity framework code first approach and have following model and db context.
public class Patient
{
public int Id { get; set; }
public string FirstName { get; set; }
}
public class PatientContext:DbContext
{
public DbSet<Patient> Persons { get; set; }
}
Now to have the model created in database I have to Run Enable Migration and if any changes then have to run Update database using package manager console.
But is there any way I can do that using code. So when ever some one run console application it will create all table schema.
class Program
{
static void Main(string[] args)
{
//Code to Create my tables
//Something similar to enable migration and update database
}
}
I can have a record created inside main app and that will create the table structure, but creating an record to create table structure seems redundant. Also , if there are any mode changes, following code will throw an exception.
Is there a better approach?
static void Main(string[] args)
{
using (PatientContext pcontext = new DatabaseMigApp.PatientContext())
{
pcontext.Patients.Add(new Patient() { FirstName = "Steve",Id = 1});
pcontext.SaveChanges();
}
}
Yes, there is. You will have to run Enable-Migrations –EnableAutomaticMigrations from package manager console first, so that it generates a Configuration class as follows:
internal sealed class Configuration : DbMigrationsConfiguration<DatabaseMigApp.PatientContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
}
protected override void Seed(DatabaseMigApp.PatientContext context)
{
}
}
Then you may update the Program.cs as follows to automatically migrate to the latest version based on your model changes:
using System.Data.Entity;
class Program
{
static void Main(string[] args)
{
using (PatientContext pcontext = new DatabaseMigApp.PatientContext())
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<PatientContext, Configuration>());
pcontext.Database.Initialize(true);
}
}
}
If you do not wish to use automatic migrations and generate them manually (for more control), you could turn off the automatic migrations and add-migration each time you change your model. The code in the program.cs would still suffice to update your database when you run the console application.
Read more about automatic migrations here
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.