How use DbContext in migration? - c#

How can I use DbContext which works with the current database (that now use in migration).
Example:
namespace Data.SqlServer.Migrations
{
[DbContext(typeof(MyDbContext))] // I want use this context
[Migration("CustomMigration_DataSeed")]
public partial class DataSeedMigration : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// add some entities
_context.User.Add(new User());
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}
Thanks for help!

Create a class for your migration configuration :
internal sealed class Configuration : DbMigrationsConfiguration<MyDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
//On true you might be losing data be aware.
AutomaticMigrationDataLossAllowed = false;
ContextKey = "Path To Your DbContext";
}
protected override void Seed(MyDbContext context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
Then reference this to your DbContext Class:
public class MyDbContext : DbContext
{
public MyDbContext()
: base("name=MyConnection")
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext,Configuration>("MyConnection"));
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//here you can MAP Your Models/Entities
}
}
Remember if you do not want to migrate several POCOs then do not add them within your OnModelCreating Method and well comment them.

Related

DbContextFactory in WPF .Net 6 project (EF, MVVM)

My application is working fine when I use a plain/classic DBContext implementation, but when I try DbContextFactory, _contextFactory.CreateDbContext() is always failing with 'null' exception. What am I missing?
My App.xaml.cs (no changes were needed in this file whilst using DBContext):
private void ConfigureServices(IServiceCollection services)
{
string defaultConnection = Settings.Default.DefaultConnection;
services.AddDbContextFactory<MyDbContext>(options => options.UseMySql(defaultConnection, ServerVersion.AutoDetect(defaultConnection)));
services.AddTransient(typeof(MainWindow));
}
MyDbContext.cs file (no changes were needed as it seems to match DbContextFactory constructor's requirements already):
public class MyDbContext : DbContext
{
public MyDbContext (DbContextOptions<MyDbContext> options)
: base(options)
{
}
// DbSets
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
string defaultConnection = Settings.Default.DefaultConnection;
var options = new DbContextOptionsBuilder<MyDbContext>()
.UseMySql(defaultConnection, ServerVersion.AutoDetect(defaultConnection))
.Options;
}
optionsBuilder.UseLazyLoadingProxies();
// To be disabled in production
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
optionsBuilder.EnableSensitiveDataLogging();
optionsBuilder.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Table building logic for EF code-first
}
}
MainWindow.xaml.cs file:
public partial class MainWindow : Window
{
private readonly IDbContextFactory<MyDbContext> _contextFactory;
private SomeVieModel _someVieModel;
public MainWindow()
{
InitializeComponent();
var _context = _contextFactory.CreateDbContext(); // Throws 'null' exception
// Probably should be instantiating a new DbContext
// in the VM itself, instead of passing it on?
_someVieModel = new SomeVieModel(_context);
}
}
I've checked numerous Blazor examples, because of the lack of WPF ones, and I feel I'm missing something very simple, some one line of DbContextFactory object instantiation? Like in this example - where is IDbContextFactory<MyDbContext> contextFactory object coming from and where is it instantiated? Thank you!
I think I've worked it out, although I'm sure some of you will be pointing out the error of my ways :)
I just realised, that I've already had my own DbContextFactory class created for database migrations, because otherwise the EF Core could not connect to the database via project's DbContext class alone.
public class MyDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
public MyDbContext CreateDbContext(string[]? args = null)
{
string defaultConnection = Settings.Default.DefaultConnection;
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseMySql(defaultConnection, ServerVersion.AutoDetect(defaultConnection));
return new MyDbContext(optionsBuilder.Options);
}
}
I've commented the code in App.xaml.cs out and initialised DbContextFactory via my own class instead of IDbContextFactory interface:
public partial class MainWindow : Window
{
private readonly MyDbContextFactory _contextFactory;
private SomeVieModel _someVieModel;
public MainWindow()
{
InitializeComponent();
_contextFactory = new MyDbContextFactory();
_someVieModel = new SomeVieModel(_contextFactory);
}
}
And called CreateDbContext() in a view model:
public class SomeVieModel : ViewModelBase
{
private readonly MyDbContextFactory _contextFactory;
public SomeVieModel(MyDbContextFactory contextFactory)
{
_contextFactory = contextFactory;
await LoadDBStuff();
}
private async Task LoadDBStuff()
{
using (var context = _contextFactory.CreateDbContext())
{
await context.SomeDataModel.LoadAsync();
SomeDataModelObservableCollection = context.SomeDataModel.Local.ToObservableCollection();
}
}
}

How do I target another database with tracker-enabled-dbcontext

I'm trying to implement the tracker-enabled-dbcontext package from the documentaion and tracker-enabled-dbcontext git repository
But I'm unable to change save changes to target a different database. I've modified my SaveChanges
public class MyDBContext : DbContext, IUnitOfWork {}
public class MacsAuditDbContext : TrackerEnabledDbContext.TrackerContext {}
in MyDBContext
public override int SaveChanges()
{
DateTime nowAuditDate = DateTime.Now;
IEnumerable<System.Data.Entity.Infrastructure.DbEntityEntry<DomainEntity>> changeSet = ChangeTracker.Entries<DomainEntity>();
if (changeSet != null)
{
foreach (System.Data.Entity.Infrastructure.DbEntityEntry<DomainEntity> entry in changeSet)
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.Created = nowAuditDate;
entry.Entity.Modified = nowAuditDate;
break;
case EntityState.Modified:
entry.Entity.Modified = nowAuditDate;
break;
}
}
}
using (MacsAuditDbContext db = new MacsAuditDbContext())
{
db.SaveChanges();
}
return base.SaveChanges();
}
in my startup class
public class Startup
{
public void Configuration(IAppBuilder app)
{
AuthConfig.Register(app);
GlobalConfiguration.Configuration
.UseSqlServerStorage("MacsAuditDbContext");
}
}
But I am still unable to save audit logs to the target(secondary) database. my Domain entires saving my primary DB but not audit logs.
Do I want to pass MyDBContext to MacsAuditDbContext? Or Am I doing something wrong? please help me.
You can try leveraging OnAuditLogGenerated event. Something along this lines:
public sealed class MyDBContext : TrackerContext
{
public MyDBContext ()
{
OnAuditLogGenerated += SaveToAnotherDb;
}
private void SaveToAnotherDb(object? sender, AuditLogGeneratedEventArgs args)
{
var auditLog = args.Log;
using (MacsAuditDbContext db = new MacsAuditDbContext())
{
db.AuditLog.Add(auditLog);
db.SaveChanges();
}
//skips saving to local database
args.SkipSavingLog = true;
}
protected override void Dispose(bool disposing)
{
OnAuditLogGenerated -= SaveToAnotherDb;
base.Dispose(disposing);
}
}

Pass connection string to EF DbContext code first

I have a context that derives from DbContext like the one below:
public class StudentContext : DbContext
{
public StudentContext(string connectionString) : base(connectionString)
{
}
protected override void OnModelCreating(DBModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<StudentContext, StudentMigrations.Configuration>());
}
public DbSet<Students> Students {get; set;}
}
I'm trying to pass the connection string by:
studentContext = new StudentContext(settings.ConnectionString)
The settings are loaded at run-time by reading a configuration file.
I've tried this and I've also tried setting the connection string inside the StudentContext constructor by using this.Database.Connection.ConnectionString.In either case, I get an exception that asks me to provide a default constructor or provide an implementation of IDbContextFactory. The only thing that works is this:
public class StudentContext : DbContext
{
public static string ConnectionString;
public StudentContext(string connectionString) : base(ConnectionString = connectionString)
{
}
//And also provide a default implementation of the DbContext constructor:
public StudentContext() : base(ConnectionString)
{
}
protected override void OnModelCreating(DBModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<StudentContext, StudentMigrations.Configuration>());
}
public DbSet<Students> Students {get; set;}
}
I am trying to reduce the use of statics in code and therefore, if I could get the first option to work, that'd be great.
Turns out that the connection string is cached in a readonly string for MigrateDatabaseToLatestVersion from this answer. I just had to update the class to:
public class StudentContext : DbContext
{
public StudentContext(string connectionString) : base(connectionString)
{
}
protected override void OnModelCreating(DBModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<StudentContext, StudentMigrations.Configuration>(true)); //Passing true here to reuse the client context triggering the migration
}
public DbSet<Student> Students {get; set;}
}
We have to specify the entity connection string. in DbContext
SqlConnectionStringBuilder sqlString = new SqlConnectionStringBuilder()
{
DataSource = "SOURAV-PC", // Server name
InitialCatalog = "efDB", //Database
UserID = "sourav", //Username
Password = "mypassword", //Password
};
//Build an Entity Framework connection string
EntityConnectionStringBuilder entityString = new EntityConnectionStringBuilder()
{
Provider = "System.Data.SqlClient",
Metadata = "res://*/testModel.csdl|res://*/testModel.ssdl|res://*/testModel.msl",
ProviderConnectionString = sqlString.ToString()
};
return entityString.ConnectionString;
}

"The operation cannot be completed because the DbContext has been disposed" #2

I'm building a simple ASP.NET API using EF and Oracle Database. When I want to get all elements from a database table the response (500) says "The operation cannot be completed because the DbContext has been disposed".
Well, I've tried to solve this problem before to post it here. But I can't. My Controller Code is as follows.
public class PruebasController : ApiController
{
//Prueba[] pruebas = new Prueba[]
//{
// new Prueba { Name = "Tomato Soup"},
// new Prueba { Name = "Yo-yo"},
// new Prueba { Name = "Hammer"}
//};
public IQueryable<Prueba> GetAllPruebas()
{
Database.SetInitializer(new DropCreateDatabaseAlways<OracleDbContext>());
using (var ctx = new OracleDbContext())
{
return ctx.Pruebas;
}
}
}
(As you see, I have a "pruebas" List and when I return it the http service works)
And this is my OracleDbContext
public class OracleDbContext : DbContext
{
public DbSet<Prueba> Pruebas { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("DATA");
}
}
You are returning an IQueryable object. Once you return, you exit your Using statement, which closes your Context. You need to enumerate using .ToList() before you exit your using statement. This will execute the query while the context is still open.
Change it to this:
public List<Prueba> GetAllPruebas()
{
using (var ctx = new OracleDbContext())
{
return ctx.Pruebas.ToList();
}
}
Also, you should add your initializer in the constructor of your context, not your GetAllPruebas method, like this:
public class OracleDbContext : DbContext
{
public OracleDbContext()
{
Database.SetInitializer<OracleDbContext>(new DropCreateDatabaseAlways<OracleDbContext>());
}
public DbSet<Prueba> Pruebas { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("DATA");
}
}
Problem Solved. I wrote CreateDatabaseIfNotExists instead DropCreateDatabaseAlways in the Database.SetInitializer and it works.

How reuse an existing DbContext as base class for a new project?

I have an existing DbContext class that I want to use as a base class for my other web projects. That DbContext contain a really complete table structure to registrer companies and peoples with related information’s.
When starting a new project, I do not want to create the same structure again and again! So I try to reuse the existing DbContext as a base class for my new project and just adding the new tables needed to make it working as wanted in the new application.
But, I cannot find a way to make it working! The DbContext base class is like below, it came from another project:
public class TemplateDbContext
{
private readonly Guid _instanceId;
bool _disposed;
public Guid InstanceId { get { return _instanceId; } }
public TemplateDbContext ()
: base("name=" + GetConnectionStringName)
{
}
public static TemplateDbContext Create()
{
return new TemplateDbContext ();
}
private void SetOneToMany(DbModelBuilder modelBuilder)
{
}
private void SetManyToMany(DbModelBuilder modelBuilder)
{
}
private void SetOneToOne(DbModelBuilder modelBuilder)
{
}
private void SetOneNavigation(DbModelBuilder modelBuilder)
{
}
private void SetKeys(DbModelBuilder modelBuilder)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUserClaim>().ToTable("MembershipClaims");
modelBuilder.Entity<IdentityUserRole>().ToTable("MembershipRoles");
modelBuilder.Entity<IdentityUserLogin>().ToTable("MembershipLogins");
modelBuilder.Entity<IdentityRole>().ToTable("Roles");
modelBuilder.Entity<Membership>().ToTable("Memberships");
SetKeys(modelBuilder);
SetOneNavigation(modelBuilder);
SetOneToOne(modelBuilder);
SetOneToMany(modelBuilder);
SetManyToMany(modelBuilder);
}
public static string GetConnectionStringName
{
get
{
...
}
}
public DbSet<UserProfile> UserProfiles
{
get;
set;
}
// I have many more DbSet defined here...
public override int SaveChanges()
{
...
}
public override async Task<int> SaveChangesAsync()
{
...
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
...
}
public void SyncObjectState<TEntity>(TEntity entity) where TEntity : class, IObjectState
{
...
}
private void SyncObjectsStatePreCommit()
{
...
}
public void SyncObjectsStatePostCommit()
{
...
}
protected override void Dispose(bool disposing)
{
...
}
}
Here is the ApplicationDbContext for my new project :
public class ApplicationDbContext : TemplateDbContext
{
public DbSet<Fruit> Fruits { get; set; }
// I have many more DbSet define here...
private void SetOneToMany(DbModelBuilder modelBuilder)
{
...
}
private void SetManyToMany(DbModelBuilder modelBuilder)
{
...
}
private void SetOneToOne(DbModelBuilder modelBuilder)
{
...
}
private void SetOneNavigation(DbModelBuilder modelBuilder)
{
...
}
private void SetKeys(DbModelBuilder modelBuilder)
{
...
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
SetKeys(modelBuilder);
SetOneNavigation(modelBuilder);
SetOneToOne(modelBuilder);
SetOneToMany(modelBuilder);
SetManyToMany(modelBuilder);
}
}
As you could guest, this is not working at all and I don’t know how I can achieve the result I want. I even don’t know if it is possible without duplicating all the code from TemplateDbContext class.
The best things that I accomplished for now, it to generate the TemplateDbContext only, by executed the Add-Migration with Package Manager Console within the new project. So the migration missed all the DbSet defined into the ApplicationDbContext class.
What kind of structure I need to do that?
You can specify the name of Context when you enable migration and using the other commands in same way, you can find more info about this here.
enable-migrations -ContextTypeName <DbContext-Name-with-Namespaces> -MigrationsDirectory:<Migrations-Directory-Name>
Add-Migration -configuration <DbContext-Migrations-Configuration-Class-with-Namespaces> <Migrations-Name>
Update-Database -configuration <DbContext-Migrations-Configuration-Class-with-Namespaces> -Verbose
Note: Don't forget to change Default Project to be the same as ApplicationDbContext in Package Manager Console.
Also i think you forgot to call base.OnModelCreating(modelBuilder) when you override the method in ApplicationDbContext.

Categories