Code First Migrations without NuGet - entity-framework

I'm wondering how I can use Entity Framework's Code First Migrations without using NuGet at all (so no commands via the package manager console).
I tried the following:
Database-Context
public sealed class MyContext : DbContext
{
private const string ConnectionStringName = "MyDatabase";
public MyContext()
: base(ConnectionStringName)
{}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
SetupMyModel(modelBuilder);
}
}
Migration-Configuration
public class MyMigrationConfiguration : DbMigrationsConfiguration<MyContext>
{
public MyMigrationConfiguration()
{
AutomaticMigrationsEnabled = false;
}
}
Database-Initializer
System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MyMigrationConfiguration>());
InitialDatabaseCreation Migration
public class InitialDatabaseCreation : DbMigration, IMigrationMetadata
{
public override void Up()
{
CreateTable("dbo.MyModel",
c => new
{
Id = c.Guid(false, true),
SomeProperty = c.Int(false)
})
.PrimaryKey(x => x.Id);
}
public override void Down()
{
DropTable("dbo.MyModel");
}
public string Id
{
get { return "0001_InitialDatabaseCreation"; }
}
public string Source
{
get { return null; }
}
public string Target
{
get { return Id; }
}
}
As you can see, I wrote a context, a migration configuration and the migration itself.
For the Migration, I'm not sure, if I implemented the IMigrationMetadata correcly. For the Target I just use the id, because I don't want to have any automatic migrations or the ability to use the package manager console. I think, this should be fine here?
I set a breakpoint at the Up method and debugged it, but it does not stop there, which means it doesn't get executed.
Therefor I want to know how to use the EF Code First migrations when writing everything manually.

Related

Database Migration on Startup

I've read a lot of articles regarding database migration on startup and no matter what approach I use my efforts aren't going anywhere. My main problem that i'm getting is no parameterless constructor defined for type startup
I have my DataContext class
public class DataContext : DbContext
{
public DataContext(DbContextOptions options) : base(options)
{
}
public DataContext()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
if (options.IsConfigured)
{
//means that context has been added during dependency injection and no further action required.
}
else
{
//means context is being accessed during Add-Migration therefore we need to set the options. The whole DI/Configuration process won't have run yet, so need some other way to get connection string.
//probably below is a bit too fancy, just hardcoding would be fine. But anyway it seems to work and transfers to different developers machines
//you must have {Values: { SqlConnectionString : xyz}} in local.settings.json in Unite.FunctionApp project dir
var localSettingsJson =
Path.Combine(local.settings.json");
var config = new ConfigurationBuilder()
.AddJsonFile(localSettingsJson, false)
.Build();
options.UseSqlServer(config["Values:SqlConnectionString"]);
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{... }
My Startup Class
// register assembly
[assembly: FunctionsStartup(typeof(Startup))]
{
// inherit FunctionsStartup
public class Startup : FunctionsStartup
{
private DataContext _context;
public Startup(DataContext context)
{
_context = context;
}
public override void Configure(IFunctionsHostBuilder builder)
{
var executionContextOptions = builder.Services.BuildServiceProvider()
.GetService<IOptions<ExecutionContextOptions>>().Value;
var config = new ConfigurationBuilder()
.SetBasePath(executionContextOptions.AppDirectory)
.AddJsonFile("local.settings.json", true)
.AddUserSecrets(Assembly.GetExecutingAssembly(), false)
.AddEnvironmentVariables()
.Build();
builder.Services.AddSingleton<IConfiguration>(config);
var sqlConnection = config["SqlConnectionString"] ??
throw new Exception("SQL Connection String Not Defined");
builder.Services.AddDbContext<DataContext>(options => options.UseSqlServer(sqlConnection));
_context.Database.MigrateAsync();
}
}
}
If I have my paramaterless DataContext method in my class why am i still getting this issue that it isn't defined?
Add your parameterless constructor before the other constructor in your DataContext class.

Dbcontext not able to migrate because schema does not exist?

I am currently writing some integrations test which affect the database just in a different schema.
I am following this guide
https://www.thinktecture.com/en/entity-framework-core/isolation-of-integration-tests-in-2-1/
My problem is with this abstract class used for creating DbContext?
public abstract class IntegrationTestsBase<T> : IDisposable
where T : DbContext
{
private readonly string _schema;
private readonly string _historyTableName;
private readonly DbContextOptions<T> _options;
protected T DbContext { get; }
protected IntegrationTestsBase()
{
_schema = Guid.NewGuid().ToString("N");
_historyTableName = "__EFMigrationsHistory";
_options = CreateOptions();
DbContext = CreateContext();
DbContext.Database.Migrate();
}
protected abstract T CreateContext(DbContextOptions<T> options,
IDbContextSchema schema);
protected T CreateContext()
{
return CreateContext(_options, new DbContextSchema(_schema));
}
private DbContextOptions<T> CreateOptions()
{
return new DbContextOptionsBuilder<T>()
.UseNpgsql($"Server=(local);Database=Demo;...",
builder => builder.MigrationsHistoryTable(_historyTableName, _schema))
.ReplaceService<IMigrationsAssembly, DbSchemaAwareMigrationAssembly>()
.ReplaceService<IModelCacheKeyFactory, DbSchemaAwareModelCacheKeyFactory>()
.Options;
}
public void Dispose()
{
DbContext.GetService<IMigrator>().Migrate("0");
DbContext.Database.ExecuteSqlCommand(
(string)$"DROP TABLE [{_schema}].[{_historyTableName}]");
DbContext.Database.ExecuteSqlCommand((string)$"DROP SCHEMA [{_schema}]");
DbContext?.Dispose();
}
}
DbContext is being created, but once the migrations is being called i get an error stating that the schema, does not exist?
which I am not sure why because i in my ensure the in my configuration
public class SchemaContext : DbContext, IDbContextSchema
{
public virtual DbSet<Schema>? SchemaModel { get; set; }
public SchemaContext()
{
}
public SchemaContext(DbContextOptions<SchemaContext> options, IDbContextSchema schema = null)
: base(options)
{
Schema = schema.Schema;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema(Schema);
//base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}
public string Schema { get; }
}
I would assume that modelBuilder.HasDefaultSchema(Schema); would have created the schema?
which does not seem to be case, hence migration fails? what am I missing? why is the schema not being created in the actual database?
In your could you are configure Context with method - UseSqlServer which is used only for MS SQL, you have to use Npgsql.EntityFrameworkCore.PostgreSql package and configure you context with UseNpgsql extension method:
.UseNpgsql("Host=localhost;Port=5432;Database=my_db;Username=postgres;Password=password");

No extension Method ToList on a dbset - really though?

Background - I am using Entity framework code version 2.1.4-rtm-31024
check out the CODE LISTING 1 - the problem (according to Ms Build Engine 15.9) is that GetAllMakes calls .ToList, but no 'ToList' method exists for a DbSet of VehicleMake. (check out Code Listing 2) to see the implementation of _vehicleContext.VehicleMakes
Why do I get a compile error? this makes no sense to me since I can call VehicleMakes.ToList() elsewhere in the code (no compiler error) no problem at all - see listing 3 for an example.
CODE LISTING 1
using System.Collections.Generic;
namespace CarPriceComparison.Models
{
public class VehicleRepository : IVehicleRepository
{
private VehicleContext _vehicleContext;
public VehicleRepository(VehicleContext dbContext_)
{
_vehicleContext = dbContext_;
}
public IEnumerable<VehicleMake> GetAllMakes()
{
return _vehicleContext.VehicleMakes.ToList();
}
}
}
CODE LISTING 2
namespace CarPriceComparison.Models
{
public class VehicleContext : DbContext
{
private IConfigurationRoot _config;
public VehicleContext(IConfigurationRoot config_, DbContextOptions
options_) : base(options_)
{
_config = config_;
}
public DbSet<VehicleMake> VehicleMakes {get; set;}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer(_config["ConnectionStrings:VehicleContextConnection"]);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<VehicleModel>()
.HasOne(p => p.Make)
.WithMany(b => b.Models)
.HasForeignKey(p => p.VehicleMakeForeignKey)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
}
}
}
CODE LISTING 3
private VehicleContext _vehicleContext;
private IMailService _mailService;
private IConfigurationRoot _config;
public HomeController(IMailService mailService_, IConfigurationRoot
config_, VehicleContext vehicleContext_)
{
_vehicleContext = vehicleContext_;
_mailService = mailService_;
_config = config_;
}
public IActionResult Index()
{
var vehicleData = _vehicleContext.VehicleMakes.ToList();
return View();
}
I think you missing an using statement.
using System.Linq;

How to change database schema on runtime in EF7 or EF core

My database have different schema depending on user selections on runtime.
My code is below:
public partial class FashionContext : DbContext
{
private string _schema;
public FashionContext(string schema) : base()
{
_schema = schema;
}
public virtual DbSet<Style> Styles { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer(#"Server=.\sqlexpress;Database=inforfashionplm;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Style>()
.ToTable("Style", schema: _schema);
}
}
Upon testing. I created a context instance with 'schema1'.
So far so good.
But when I create another context instance with different schema 'schema2', the resulting data in which the schema is still on 'schema1'.
Here is the implementation:
using (var db = new FashionContext("schema1"))
{
foreach (var style in db.Styles)
{
Console.WriteLine(style.Name);
}
}
Console.ReadLine();
Console.Clear();
using (var db = new FashionContext("schema2"))
{
foreach (var style in db.Styles)
{
Console.WriteLine(style.Name);
}
}
Console.ReadLine();
Later I noticed that the OnModelCreating is called only one time, so it is never called again when you create a new context instance of the same connection string.
Is it possible to have dynamic schema on runtime? Note: this is possible in EF6
One of possible way was mentioned above, but briefly, so I will try to explain with examples.
You ought to override default ModelCacheKeyFactory and ModelCacheKey.
ModelCachekeyFactory.cs
internal sealed class CustomModelCacheKeyFactory<TContext> : ModelCacheKeyFactory
where TContext : TenantDbContext<TContext>
{
public override object Create(DbContext context)
{
return new CustomModelCacheKey<TContext>(context);
}
public CustomModelCacheKeyFactory([NotNull] ModelCacheKeyFactoryDependencies dependencies) : base(dependencies)
{
}
}
ModelCacheKey.cs, please review Equals and GetHashCode overridden methods, they are not best one and should be improved.
internal sealed class ModelCacheKey<TContext> : ModelCacheKey where TContext : TenantDbContext<TContext>
{
private readonly string _schema;
public ModelCacheKey(DbContext context) : base(context)
{
_schema = (context as TContext)?.Schema;
}
protected override bool Equals(ModelCacheKey other)
{
return base.Equals(other) && (other as ModelCacheKey<TContext>)?._schema == _schema;
}
public override int GetHashCode()
{
var hashCode = base.GetHashCode();
if (_schema != null)
{
hashCode ^= _schema.GetHashCode();
}
return hashCode;
}
}
Register in DI.
builder.UseSqlServer(dbConfiguration.Connection)
.ReplaceService<IModelCacheKeyFactory, CustomModelCacheKeyFactory<CustomContext>>();
Context sample.
public sealed class CustomContext : TenantDbContext<CustomContext>
{
public CustomContext(DbContextOptions<CustomContext> options, string schema) : base(options, schema)
{
}
}
You can build the model externally and pass it into the DbContext using DbContextOptionsBuilder.UseModel()
Another (more advanced) alternative is to replace the IModelCacheKeyFactory to take schema into account.
I found a way to recreate the compiled model on each context creation.
public partial class MyModel : DbContext {
private static DbConnection _connection
{
get
{
//return a new db connection
}
}
private static DbCompiledModel _model
{
get
{
return CreateModel("schema name");
}
}
public MyModel()
: base(_connection, _model, false)
{
}
private static DbCompiledModel CreateModel(string schema)
{
var modelBuilder = new DbModelBuilder();
modelBuilder.HasDefaultSchema(schema);
modelBuilder.Entity<entity1>().ToTable(schema + ".entity1");
var builtModel = modelBuilder.Build(_connection);
return builtModel.Compile();
}
}

Entity framework add-migration scaffold with custom Migration base

I need to scaffold migration with add-migration from Package Manager Console with my custom Migration base CustomMigration which is derived from DbMigration.
public partial class NewMigration: CustomMigration
{
public override void Up()
{
}
public override void Down()
{
}
}
I can use different command if needed. I don't have any skills in powershell scripting. How can i achieve this?
I Created new class which generated my migrations:
public class AuditMigrationCodeGenerator : CSharpMigrationCodeGenerator
{
protected override void WriteClassStart(string #namespace, string className, IndentedTextWriter writer, string #base, bool designer = false, IEnumerable<string> namespaces = null)
{
#base = #base == "DbMigration" ? "AuditMigration" : #base;
var changedNamespaces = namespaces?.ToList() ?? new List<string>();
changedNamespaces.Add("Your.Custom.Namespace");
base.WriteClassStart(#namespace, className, writer, #base, designer, changedNamespaces);
}
}
In Configuration.cs :
internal sealed class Configuration : DbMigrationsConfiguration<EfDataAccess>
{
public Configuration()
{
this.AutomaticMigrationsEnabled = false;
CodeGenerator = new AuditMigrationCodeGenerator();
}
}
And it will use your Custom code generator, which generates migrations with my desired custom migration base.
For more info: https://romiller.com/2012/11/30/code-first-migrations-customizing-scaffolded-code/
run command add-migration NewMigration. It will add new migration with name "NewMigration". If there is no changes in model, the migration will be empty:
public partial class NewMigration : DbMigration
{
public override void Up()
{
}
public override void Down()
{
}
}
Change base class of NewMigration to CustomMigration:
public partial class NewMigration : CustomMigration
{
public override void Up()
{
}
public override void Down()
{
}
}
modify NewMigration as you wish
run update-database to apply migration