How do I connect custom logging to my ef6 migrations? - c#

I have a DbMigrationsConfiguration that looks like this:
internal sealed class Configuration : DbMigrationsConfiguration<DatabaseProject.DB>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
AutomaticMigrationDataLossAllowed = false;
}
}
Elsewhere, in my DbContext class I have:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DB, DbProject.Migrations.Configuration>(useSuppliedContext: true));
// and so on...
And I want to use the MigrationsLogger to record some information when a migration is applied. So I set up a simple class like this using serilog:
public class EfLogger : MigrationsLogger
{
public override void Info(string message)
{
Log.Logger.Information("Machine {name} reported EF Migration Message: {message}", Environment.MachineName,
message);
}
public override void Warning(string message)
{
Log.Logger.Warning("Machine {name} reported EF Migration Warning: {message}", Environment.MachineName,
message);
}
public override void Verbose(string message)
{
Log.Logger.Verbose("Machine {name} reported EF Migration verbose message: {message}", Environment.MachineName,
message);
}
}
So how do I change my configuration to use the new logger? I can't find any examples or documentation on this anywhere.

I looked into how this works and I think you won't be able to use MigrateDatabaseToLatestVersion directly. You should be able to override it though. If you look into the actual logic there it's actually quite simple:
https://github.com/dotnet/ef6/blob/master/src/EntityFramework/MigrateDatabaseToLatestVersion%60.cs
public virtual void InitializeDatabase(TContext context)
{
Check.NotNull(context, "context");
var migrator = new DbMigrator(_config, _useSuppliedContext ? context : null);
migrator.Update();
}
To add your logging to it, create a new MigrateDatabaseToLatestVersionWithLogging implementation that inherits from the standard MigrateDatabaseToLatestVersion, then override the method and introduce a logging decorator before calling update. Something like this (untested):
https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.migrations.infrastructure.migratorloggingdecorator?view=entity-framework-6.2.0
public override void InitializeDatabase(TContext context)
{
_ = context ?? throw new ArgumentNullException(nameof(context));
var migrator = new MigratorLoggingDecorator(
new DbMigrator(_config, _useSuppliedContext ? context : null),
new EfLogger());
migrator.Update();
}

Related

Write a test for SQL Server using EF in C#

I'm trying to test the below function using Nunit:
public static void Create(Contact contact)
{
using (var db = new PhonebookContext())
{
db.Database.EnsureCreated();
try
{
db.Contacts.Add(contact);
db.SaveChanges();
Console.WriteLine($"Successfully added {contact.Name}!");
}
catch
{
Console.WriteLine(Helpers.CreateErrorMessage, contact.Name);
}
}
}
This is my current test:
[Test]
public void Create_Contact_DbRowIsAdded()
{
var contact = new Contact { Name = "Abhinav", PhoneNumber = 1234567890, };
SqlAccess.Create(contact);
Assert.Equals(contact, SqlAccess.GetLastContact());
}
The test fails with the following error:
System.InvalidOperationException : No database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.
If it helps, here's the code for the dbContext (which I believe the test has no access to):
public class PhonebookContext : DbContext
{
public DbSet<Contact> Contacts => Set<Contact>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
try
{
optionsBuilder.UseSqlServer(System.Configuration
.ConfigurationManager
.ConnectionStrings["SQLServer"]
.ConnectionString);
}
catch (Exception ex)
{
Console.WriteLine("An unknown error occurred while creating the database. Please make sure SQL server is running.");
Console.WriteLine(ex.Message);
}
}
}
The function works perfectly, except when it is put through the test.
Am I missing something?
That's because you do not provide in your's PhonebookContext the db configuration for Contact class (which is db entry i guess).
So in order to fix that you should do something simmilar:
internal class ContactDbConfiguration : IEntityTypeConfiguration<Contact>
{
public void Configure(EntityTypeBuilder<TaskDto> builder)
{
// register your properties of contact class here for example:
builder.HasKey(t => t.Id);
}
}
And please add on model creating method in your PhonebookContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new ContactDbConfiguration());
base.OnModelCreating(modelBuilder);
}
Finally your dbContext should be like this:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// your code here
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new ContactDbConfiguration());
base.OnModelCreating(modelBuilder);
}
public DbSet<Contact> Contacts { get; set; }

How to unlock the Sqlite database file in EntityFramework.Core

I have a project that uses Microsoft.EntityFrameworkCore.Sqlite 6.0.5 on net6.0. I am expecting the Sqlite database file to be unlocked after the DbContext has been disposed.
However, the behaviour I am observing is that the Sqlite database remains locked after the DbContext has been disposed and finalised. There is a project that reproduces the behaviour here.
How can I unlock the database file?
My DbContext looks like this:
public class MyContext : DbContext
{
~MyContext()
{
Console.WriteLine("Finaliser was called.");
}
public override void Dispose()
{
base.Dispose();
Console.WriteLine("Dispose was called.");
}
public static readonly string DbFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "_temp.db");
public DbSet<Foo> Summaries { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Foo>().HasKey(nameof(Foo.Id));
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlite($"Data Source={DbFile}");
}
}
I am using it like this:
public static void AddItem()
{
using var ctx = new MyContext();
ctx.Database.EnsureCreated();
ctx.Summaries.Add(new Foo {Bar = "Foo"});
ctx.SaveChanges();
}
ClearAllPools() or specify no pooling in connection string (Pooling=false)
https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/connection-strings#pooling

C# EF Code first - init database (create table) and enable migrations

I'm updating a webform app to a mvc app but I have an issue.
I would like to create automatically the AspNetUsers tables when I try, for the first time,to log in through the webApp
Below the error messages:
The target context 'PROJECT.Data.DataContext' is not constructible.
Add a default constructor or provide an implementation of
IDbContextFactory.
Model compatibility cannot be checked because the database does not contain model metadata. Model compatibility can only be checked for
databases created using Code First or Code First Migrations.
The first one is fired with the code below:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DataContext, MyConfiguration>());
public class MyConfiguration : DbMigrationsConfiguration<DataContext>
{
public MyConfiguration()
{
this.AutomaticMigrationsEnabled = true;
}
protected override void Seed(DataContext context)
{
InitializeIdentityForEF(context);
base.Seed(context);
}
public void InitializeIdentityForEF(DataContext db)
{
// create admin user with role and claims
}
}
The second one:
Database.SetInitializer(new ApplicationDbInitializer());
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<DataContext>
{
protected override void Seed(DataContext context)
{
InitializeIdentityForEF(context);
base.Seed(context);
}
public void InitializeIdentityForEF(DataContext db)
{
// create admin user with role and claims
}
}
I think that I can fix this issue if I enable the the migration through the console but I would like to implement everything in the code. Any idea to mix theses two code above?
NOTE: I just want to create the AspNetUser table because I'm using "Database First" once they are created.
public class DataContext : DbContext, IEntitiesContext
{
private static readonly object Lock = new object();
private static bool _databaseInitialized;
public DataContext(string nameOrConnectionString, ILogger logger) : base(nameOrConnectionString)
{
Database.Log = logger.Log;
if (_databaseInitialized)
{
return;
}
lock (Lock)
{
if (!_databaseInitialized)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DataContext, MyConfiguration>());
Database.SetInitializer(new ApplicationDbInitializer());
_databaseInitialized = true;
}
}
}

Automapper, Mapping config isn't persisting

So the problem.
Ive added
AutoMapperConfig.Configure();
to the application_Start in global.asax
it runs the code
Mapper.Initialize(x =>
{
x.AddProfile<DomainToViewModelMappingProfile>();
x.AddProfile<ViewModelToDomainMappingProfile>();
});
Mapper.AssertConfigurationIsValid();
which runs
public class DomainToViewModelMappingProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<DBO.User, ViewModels.UserViewModel>();
}
}
and
public class ViewModelToDomainMappingProfile : Profile
{
protected override void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ViewModels.UserViewModel, DBO.User>();
});
}
}
and everything compiles and runs fine.
but in the controller:
UserViewModel model = new UserViewModel();
User user = userService.GetUser(2);
model = Mapper.Map<User, UserViewModel>(user); //this line fails as mapping doesnt exist
return View();
but if i add the mapping config in the controller method
Mapper.CreateMap<ViewModels.UserViewModel,DBO.User>();
UserViewModel model = new UserViewModel();
User user = userService.GetUser(2);
model = Mapper.Map<User, UserViewModel>(user); //Works great
return View();
it works fine.
ignore the different syntax with automapper. Ive tried the deprecated and new way of mapping and both fail.
Thanks
The problem is that you're calling Initialize method inside your Profile which leads to overriding your already existed mappings:
public class ViewModelToDomainMappingProfile : Profile
{
protected override void Configure()
{
// you should not to call Initialize method inside your profiles.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ViewModels.UserViewModel, DBO.User>();
});
}
}
And here, you have two ways:
Way #1 (using the static API - deprecated)
public class DomainToViewModelMappingProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<DBO.User, ViewModels.UserViewModel>();
}
}
public class ViewModelToDomainMappingProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<ViewModels.UserViewModel, DBO.User>();
}
}
// initialize your mapper by provided profiles
Mapper.Initialize(x =>
{
x.AddProfile<DomainToViewModelMappingProfile>();
x.AddProfile<ViewModelToDomainMappingProfile>();
});
Mapper.AssertConfigurationIsValid();
Way #2 (using the instance API)
// in this case just call CreateMap from Profile class - its the same as CreateMap on `cfg`
public class DomainToViewModelMappingProfile : Profile
{
public DomainToViewModelMappingProfile()
{
CreateMap<DBO.User, ViewModels.UserViewModel>();
}
}
public class ViewModelToDomainMappingProfile : Profile
{
public ViewModelToDomainMappingProfile()
{
CreateMap<ViewModels.UserViewModel, DBO.User>();
}
}
// initialize you mapper config
var config = new MapperConfiguration(cfg => {
cfg.AddProfile<DomainToViewModelMappingProfile>();
cfg.AddProfile<ViewModelToDomainMappingProfile>();
});
// and then use it
var mapper = config.CreateMapper();
// or
var mapper = new Mapper(config);
var dest = mapper.Map<Source, Dest>(new Source());
In the Way #2 you will need to store you mapper configuration somewhere (static field, DI), and then use it inside your controller. I would like to suggest to inject the Mapper instance into your controller (e.g. using some DI container).
Hope it will help.
try to override "ProfileName" :
public class DomainToViewModelMappingProfile : Profile
{
public override string ProfileName
{
get
{
return "DomainToViewModelMappingProfile";
}
}
protected override void Configure()
{
Mapper.CreateMap<DBO.User, ViewModels.UserViewModel>();
}
}
OK, thanks to MaKCbIMKo for pointing me in the right direction
As described i dont have to initialize as its already being done in the automapperconfig.
the syntax once in the profile is simply.
CreateMap<ViewModels.UserViewModel, DBO.User>();

Entity Framework Migrations - Enable AutoMigrations along with added migration

I'm utilizing Entity Framework 4.3 Migrations in my project. I would like to use Automatic migrations so that when I make modifications to my domain objects and my context class, my database automatically updates when I run the project. I have this working so far.
I would also like to use some Added Migrations in addition to the automatic migrations, and I would like the application to automatically jump to the latest version (based on my added migrations) when I run the application.
In order to do this I have placed this in the global.asax file...
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Core.Migrations.Configuration>());
Now this works, but when I do this it no longer automatically updates the database based on my domain objects.
I would like to be able to completely delete the database and then run the application and have all the automatic migrations run and then have my explicit migrations run and bring the database up to the latest version.
I know I've had this working in a previous project, but I'm not sure what I'm doing wrong in this instance.
Thanks
You need to pass a configuration that has the AutomaticMigrationsEnabled set to true in the constructor. Something like this should help:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MyConfiguration>());
with MyConfiguration being something like:
public class MyConfiguration : Core.Migrations.Configuration
{
public MyConfiguration { this.AutomaticMigrationsEnabled = true; }
}
DISCLAIMER: Just hacked this in, so small tweaks might be required to get this to compile
EDIT:
Just checked with EF 4.3.1 and the code is like this for the initializer:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DataContext, MyConfiguration>());
and this for the configuration class:
public class MyConfiguration : System.Data.Entity.Migrations.DbMigrationsConfiguration<DataContext>
{
public MyConfiguration()
{
this.AutomaticMigrationsEnabled = true;
}
}
After banging my head on this for several hours, I finally came up with a solution that creates the database if necessary or upgrades it if out of date. We use this technique in Gallery Server Pro to make it easy to install the first time or upgrade previous versions.
private static void InitializeDataStore()
{
System.Data.Entity.Database.SetInitializer(new System.Data.Entity.MigrateDatabaseToLatestVersion<GalleryDb, GalleryDbMigrationConfiguration>());
var configuration = new GalleryDbMigrationConfiguration();
var migrator = new System.Data.Entity.Migrations.DbMigrator(configuration);
if (migrator.GetPendingMigrations().Any())
{
migrator.Update();
}
}
public sealed class GalleryDbMigrationConfiguration : DbMigrationsConfiguration<GalleryDb>
{
protected override void Seed(GalleryDb ctx)
{
MigrateController.ApplyDbUpdates();
}
}
I wrote up a blog post with a few more details:
Using Entity Framework Code First Migrations to auto-create and auto-update an application
Same solution that Roger did but using an static constructor on the DbContext.
Full code below.... this allow the initialization code to live on the class itself and is self-invoked at the first instantiation of the DataDbContext class.
public partial class DataDbContext : DbContext
{
public DataDbContext()
: base("name=DefaultConnection")
{
}
static DataDbContext() // This is an enhancement to Roger's answer
{
Database.SetInitializer(new DataDbInitializer());
var configuration = new DataDbConfiguration();
var migrator = new DbMigrator(configuration);
if (migrator.GetPendingMigrations().Any())
migrator.Update();
}
// DbSet's
public DbSet<CountryRegion> CountryRegion { get; set; }
// bla bla bla.....
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
//Configuration.ValidateOnSaveEnabled = false;
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.AddFromAssembly(Assembly.GetExecutingAssembly()); // Discover and apply all EntityTypeConfiguration<TEntity> of this assembly, it will discover (*)
}
}
internal sealed class DataDbInitializer : MigrateDatabaseToLatestVersion<DataDbContext, DataDbConfiguration>
{
}
internal sealed class DataDbConfiguration : DbMigrationsConfiguration<DataDbContext>
{
public DataDbConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(DataDbContext context)
{
DataSeedInitializer.Seed(context);
base.Seed(context);
}
}
internal static class DataSeedInitializer
{
public static void Seed(DataDbContext context)
{
SeedCountryRegion.Seed(context);
// bla bla bla.....
context.SaveChanges();
}
}
internal static class SeedCountryRegion
{
public static void Seed(DataDbContext context)
{
context.CountryRegion.AddOrUpdate(countryRegion => countryRegion.Id,
new CountryRegion { Id = "AF", Name = "Afghanistan" },
new CountryRegion { Id = "AL", Name = "Albania" },
// bla bla bla.....
new CountryRegion { Id = "ZW", Name = "Zimbabwe" });
context.SaveChanges();
}
}
public class CountryRegionConfiguration : EntityTypeConfiguration<CountryRegion> // (*) Discovered by
{
public CountryRegionConfiguration()
{
Property(e => e.Id)
.IsRequired()
.HasMaxLength(3);
Property(e => e.Name)
.IsRequired()
.HasMaxLength(50);
}
}
public partial class CountryRegion : IEntity<string>
{
// Primary key
public string Id { get; set; }
public string Name { get; set; }
}
public abstract class Entity<T> : IEntity<T>
{
//Primary key
public abstract T Id { get; set; }
}
public interface IEntity<T>
{
T Id { get; set; }
}
We can see that the Seed method is running again and again..
We can avoid this by checking if a migration already exits, since one is applied automatically when the database is create.. then we can refactor the DataDbConfiguration as follows...
internal sealed class DataDbConfiguration : DbMigrationsConfiguration<DataDbContext>
{
private readonly bool _isInitialized;
public DataDbConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
var migrator = new DbMigrator(this);
_isInitialized = migrator.GetDatabaseMigrations().Any();
}
protected override void Seed(DataDbContext context)
{
InitializeDatabase(context);
}
public void InitializeDatabase(DataDbContext context)
{
if (!_isInitialized)
{
if (context.Database.Connection.ConnectionString.Contains("localdb"))
{
DataSeedInitializer.Seed(context); // Seed Initial Test Data
}
else
{
// Do Seed Initial Production Data here
}
}
else
{
// Do any recurrent Seed here
}
}
}
Here is my current solution, which I'm not completely satisfied with.
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
var context = new KCSoccerDataContext();
var initializeDomain = new CreateDatabaseIfNotExists<KCSoccerDataContext>();
var initializeMigrations = new MigrateDatabaseToLatestVersion<KCSoccerDataContext, Core.Migrations.Configuration>();
initializeDomain.InitializeDatabase(context);
initializeMigrations.InitializeDatabase(context);
}
I'm actually creating two different initializers. The first, using CreateDatabaseIfNotExists, succcessfully goes through and creates tables based on my Domain objects. The second, using MigrateDatabaseToLatestVersion, executes all of my explicit migrations.
I don't like it because Automatic Migrations are basically disabled. So in order to add or change my Domain model I have to completely drop the database and recreate it. This won't be acceptable once I've moved the application to production.
If your application contains Startup.cs class, you can use DbMigrator Class as follows
Go to your App_Start folder, open Startup.Auth
Paste these lines of code inside of ConfigureAuth method
var configuration = new Migrations.Configuration();
var dbmigrator = new DbMigrator(configuration);
dbmigrator.Update();
NOTE: Remember to use this namespace- using System.Data.Entity.Migrations;
what this does is to update your database to the latest version anytime the application starts up
You just need to do
private static void InitializeDataStore()
{
System.Data.Entity.Database.SetInitializer(new System.Data.Entity.MigrateDatabaseToLatestVersion<GalleryDb, GalleryDbMigrationConfiguration>());
System.Data.Entity.Database.Initialize(false);
}

Categories

Resources