I asked a question a while back about seamless updates:
Seamless EF Migrations from Staging > Production with Schema Change
I think a simple way to achieve this is to simply suppress the error The model backing the 'ApplicationDbContext' context has changed - this way I can
Publish my app to a staging slot
Test migration against a staging db
Swap staging/production
Migrate directly to Prod
This is what I do now, but between 3 and 4, there are a few seconds where the dbContext throws that error. If I could simply suppress that error... we could eliminate 95% of those errors across those few seconds (the 5% being errors that we catch resulting from the drifted model).
Is there a way to suppress this error but keep migrations enabled?
Update:
Using the link here I was able to test out the following:
public ApplicationContext()
: base("name=DefaultConnection") {
this.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
Database.SetInitializer(new SuppressedInitializer<ApplicationContext>());
}
public class SuppressedInitializer<TContext> : IDatabaseInitializer<TContext>
where TContext : DbContext {
public void InitializeDatabase(TContext context) {
if (!context.Database.Exists()) {
throw new ConfigurationException(
"Database does not exist");
}
else {
if (!context.Database.CompatibleWithModel(true)) {
var contextException = new InvalidOperationException("The database is not compatible with the entity model.");
Elmah.ErrorSignal.FromCurrentContext().Raise(contextException);
// intentionally don't throw so that the application may continue
}
}
}
}
contextException is created when I expect it to and I believe this is a solution.
Related
I'm using the in memory database provider for integration tests however I don't seem to be able to update a record. I've run the same code against a real SQL database and everything gets updated fine. Here is my test fixture code.
Test Fixture:
public class TestFixture<TStartup> : IDisposable
{
private readonly TestServer _testServer;
public HttpClient TestClient { get; }
public IDatabaseService DbContext { get { return _testServer.Host.Services.GetService<DatabaseService>(); } }
public TestFixture() : this(Path.Combine("src")) { }
protected TestFixture(string relativeTargetProjectPatentDir)
{
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Testing");
var builder = new WebHostBuilder()
.ConfigureServices(services =>
{
services.AddDbContext<DatabaseService>(options =>
options.UseInMemoryDatabase("TestDB")
.EnableSensitiveDataLogging());
})
.UseEnvironment("Testing")
.UseStartup<Startup>();
_testServer = new TestServer(builder)
{
BaseAddress = new Uri("http://localhost:5010")
};
TestClient = _testServer.CreateClient();
TestClient.BaseAddress = _testServer.BaseAddress;
}
public void Dispose()
{
TestClient.Dispose();
_testServer.Dispose();
}
}
I've spent most of the day googling this and not come across any other people talking about it so I'm assuming its probably my issue rather than a EF bug. I'm sure someone would have noticed a DB that you can't update.
Updating works with Singleton but I have CQRS architecture and to check if the entry was updated in e2e test I have to reload entry
Context.Entry(entity).Reload();
I hope that this can help someone
It turned out that changing the lifetime of my DbContext in my test fixture to singleton solved my issue.
Well it can be that DbContext is used in wrong way. I had the same problem. I used the DbContext in same way as you. I simply returned the instance by .Host.Services.GetService<TContext>. The problem with this approach is that DbContext will never release tracked entities so either you set entity State as EntityState.Detached and you force DbContext to reload it, or you will use scopes.
using (var scope = _testServer.Host.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<DatabaseService>();
//make any operations on dbContext only in scope
}
Adding to Chris's answer. Here is an example of what I had vs. what fixed the issue:
services.AddDbContext<TestDbContext>(options => {
options.UseInMemoryDatabase("TestDb");
});
to
var options = new DbContextOptionsBuilder<TestDbContext>()
.UseInMemoryDatabase(databaseName: "TestDb")
.Options;
services.AddSingleton(x => new TestDbContext(options));
Using AsNoTracking behavior may additionally work below,
services.AddDbContext<TestDbContext>(
a => a.UseInMemoryDatabase(databaseName: "TestDb").UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking),
ServiceLifetime.Singleton)
Also, how are you updating record? This seems to track in EFCore InMemory,
_dbContext.Entry(modifyItem).State = EntityState.Modified;
However, this doesn't seem to work as much.
_dbContext.Entry(existingItem).CurrentValues.SetValues(modifyItem);
I am trying to create a Microservices architecture. I have a stateless service combined with Entity Framework deployed on the Azure Service Cluster Fabric.
However my problem is when i have a Initializer with DropCreateDatabaseAlways the database is removed but not recreated.
I have the following Initializer:
class CompanyInitializer : DropCreateDatabaseAlways<CompanyContext>
{
protected override void Seed(CompanyContext context)
{
var companies = new List<Company>
{
new Company { Name = "AAA", City = "Eindhoven", Streetname="Street 12" },
new Company { Name = "BBB", City = "Rotterdam", Streetname = "Street 12" },
new Company { Name = "CCC", City = "Eindhoven", Streetname = "Street 12" }
};
companies.ForEach(s => context.Companies.Add(s));
context.SaveChanges();
base.Seed(context);
}
}
With the following context:
public class CompanyContext : DbContext
{
public CompanyContext(string connectionString) : base(connectionString)
{
this.Database.Connection.ConnectionString = connectionString;
Database.SetInitializer<CompanyContext>(new CompanyInitializer());
}
public DbSet<Company> Companies { get; set; }
}
And i am connecting these through the constructor of the stateless service:
public StatelessServiceCompany(StatelessServiceContext context)
: base(context)
{
_databaseConnectionstring = WebConfigurationManager.AppSettings["Entity.Framework.ConnectionString"];
_context = new CompanyContext(_databaseConnectionstring);
new CompanyInitializer().InitializeDatabase(_context);
}
And the connectionstring is as followed:
Data Source=*****.*****.****.***;Initial Catalog=******;Integrated Security=False;User ID=********;Password=********;Connect Timeout=30;Encrypt=True;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False
The problem is that the database is dropped but never recreated. I believe there are rights missing to create a database through code on the Azure platform.
Secondly Service cluster fabric is not letting me enable migrations for the project. When enabling migrations via the Package manager console i get the following error:
"System.BadImageFormatException: Could not load file or assembly
'StatelessServiceCompany' or one of its dependencies. An attempt was
made to load a program with an incorrect format."
How can i solve this. I don't want to make a replicate of Entity Framework via Ado.net.
Edit
I solved my problem by deleting the Initializer and enabling migrations. The solution for enabling migration was in the Platform Target under Properties > Build. Switching the platform target between x64 and x86 seems to do the trick. The seed function in the configuration file is also a kind of initializer.
You questions consists of two issues:
Database initialisation and
Schema migrations
of which I will only address the first one as that is your primary concern.
These two issues might be connected as well as might not.
The initializer DropCreateDatabaseAlways, unsuprisingly, drops the database when you run the application but it does not recreate a new one immediately. Instead, it waits for the first context usage in particular application domain and only then recreates it.
https://msdn.microsoft.com/en-us/library/gg679506(v=vs.113).aspx
I am attempting to use code first migrations for Entity Framework 6.0
This is for a desktop (WPF) app which is currently in use at a couple of sites with their own copy of the database, so running update-database from the package manager console is of no use.
I had been hitherto using automatic migrations, but I now need to apply a more complex migration than this will allow. The new migration does get applied properly if
update-database -force
option is used (of course, restoring the database to its pre-updated state before testing what follows)
The DbConfiguration (which had been working with AutomaticMigrations) had been set up like so:
[DbConfigurationType(typeof(ContextCeConfiguration))]
public class TrialDataContext : DbContext, ITrialDataContext
{
...
}
public class ContextCeConfiguration : DbConfiguration
{
public ContextCeConfiguration()
{
SetProviderServices(
SqlCeProviderServices.ProviderInvariantName,
SqlCeProviderServices.Instance);
SetDatabaseInitializer<TrialDataContext>(new DataContextInitialiser());
}
}
class DataContextInitialiser : MigrateDatabaseToLatestVersion<TrialDataContext,TrialDataConfiguration>
{
}
internal sealed class TrialDataConfiguration : DbMigrationsConfiguration<BlowTrial.Domain.Providers.TrialDataContext>
{
public TrialDataConfiguration()
{
AutomaticMigrationsEnabled = true;
}
}
I have changed the DbMigrationsConfiguration class to:
internal sealed class TrialDataConfiguration : DbMigrationsConfiguration<BlowTrial.Domain.Providers.TrialDataContext>
{
public TrialDataConfiguration()
{
AutomaticMigrationsEnabled = false;
DbMigrator migrator = new DbMigrator(this);
if (migrator.GetPendingMigrations().Any())
{
migrator.Update();
}
}
However, the line:
DbMigrator migrator = new DbMigrator(this);
Is causing the exception:
ValueFactory attempted to access the Value property of this instance.
Can someone please suggest how/where I should instantiate the instance of DbMigrator so that updates can be programatically applied
I am attempting to use Entity Framework code based migrations with my web site. I currently have a solution with multiple projects in it. There is a Web API project which I want to initialize the database and another project called the DataLayer project. I have enabled migrations in the DataLayer project and created an initial migration that I am hoping will be used to create the database if it does not exist.
Here is the configuration I got when I enabled migrations
public sealed class Configuration : DbMigrationsConfiguration<Harris.ResidentPortal.DataLayer.ResidentPortalContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(Harris.ResidentPortal.DataLayer.ResidentPortalContext 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" }
// );
//
}
}
The only change I made to this after it was created was to change it from internal to public so the WebAPI could see it and use it in it's databaseinitializer. Below is the code in the code in the Application_Start that I am using to try to initialize the database
Database.SetInitializer(new MigrateDatabaseToLatestVersion<ResidentPortalContext, Configuration>());
new ResidentPortalUnitOfWork().Context.Users.ToList();
If I run this whether or not a database exists I get the following error
Directory lookup for the file "C:\Users\Dave\Documents\Visual Studio 2012\Projects\ResidentPortal\Harris.ResidentPortal.WebApi\App_Data\Harris.ResidentPortal.DataLayer.ResidentPortalContext.mdf" failed with the operating system error 2(The system cannot find the file specified.).
CREATE DATABASE failed. Some file names listed could not be created. Check related errors.
It seems like it is looking in the totally wrong place for the database. It seems to have something to do with this particular way I am initializing the database because if I change the code to the following.
Database.SetInitializer(new DropCreateDatabaseAlways<ResidentPortalContext>());
new ResidentPortalUnitOfWork().Context.Users.ToList();
The database will get correctly created where it needs to go.
I am at a loss for what is causing it. Could it be that I need to add something else to the configuration class or does it have to do with the fact that all my migration information is in the DataLayer project but I am calling this from the WebAPI project?
I have figured out how to create a dynamic connection string for this process. You need to first add this line into your EntityFramework entry on Web or App.Config instead of the line that gets put there by default.
<defaultConnectionFactory type="<Namespace>.<ConnectionStringFacotry>, <Assembly>"/>
This tells the program you have your own factory that will return a DbConnection. Below is the code I used to make my own factory. Part of this is a hack to get by the fact that a bunch of programmers work on the same set of code but some of us use SQL Express while others use full blown SQL Server. But this will give you an example to go by for what you need.
public sealed class ResidentPortalConnectionStringFactory: IDbConnectionFactory
{
public DbConnection CreateConnection(string nameOrConnectionString)
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(ConfigurationManager.ConnectionStrings["PortalDatabase"].ConnectionString);
//save off the original catalog
string originalCatalog = builder.InitialCatalog;
//we're going to connect to the master db in case the database doesn't exist yet
builder.InitialCatalog = "master";
string masterConnectionString = builder.ToString();
//attempt to connect to the master db on the source specified in the config file
using (SqlConnection conn = new SqlConnection(masterConnectionString))
{
try
{
conn.Open();
}
catch
{
//if we can't connect, then append on \SQLEXPRESS to the data source
builder.DataSource = builder.DataSource + "\\SQLEXPRESS";
}
finally
{
conn.Close();
}
}
//set the connection string back to the original database instead of the master db
builder.InitialCatalog = originalCatalog;
DbConnection temp = SqlClientFactory.Instance.CreateConnection();
temp.ConnectionString = builder.ToString();
return temp;
}
}
Once I did that I coudl run this code in my Global.asax with no issues
Database.SetInitializer(new MigrateDatabaseToLatestVersion<ResidentPortalContext, Configuration>());
using (ResidentPortalUnitOfWork temp = new ResidentPortalUnitOfWork())
{
temp.Context.Database.Initialize(true);
}
I'm trying to share a simple DbContext with 4 DbSets among multiple repositories, each of my repositories inherit from this base class
public class CodeFirstRepository : IDisposable
{
private static MyContext _ctx = new MyContext();
protected MyContext Context
{
get { return _ctx; }
}
public void Dispose()
{
if (Context != null)
{
Context.Dispose();
}
}
}
Question: is this an appropriate way to share a connection between repositories?
I'm getting intermittent failures in my unit tests when accessing the various repositories. An exception is thrown from the repository method GetEntityByName
public IOfferResult GetEntityByName(string name)
{
return Context.Entities.Where(o => o.Name == name).FirstOrDefault()
}
Test method
Tests.Service.TestDelete
threw exception: System.ObjectDisposedException: The ObjectContext
instance has been disposed and can no longer be used for operations
that require a connection.
if the database already exists, the code executes as expected. it also works when i change the implementation of GetEntityByName(string name) to the following non-performant code
public IOfferResult GetEntityByName(string name)
{
foreach (OfferResult offer in Context.Offers)
{
if (offerName.ToLower() == offer.Name.ToLower())
{
return offer;
}
}
}
Question: what is going on here?
bear in mind that if the database exists when i run the tests i don't get the error at all.
tia,
jt
This problem is arising because you are treating the DbContext like a singleton by declaring it as a static field, but then you are treating it like it like a transient instance by disposing it as soon as any instance of CodeFirstRepository gets disposed. For example:
using (var r = new PersonRepository())
{
// do something
} // When you hit the end of this block, your static DbContext is disposed.
using (var r = new IOfferRepository())
{
r.GetEntityByName("test"); // this will fail because the context is still disposed.
}
You should not share contexts this way. If you really want all of your repositories to use a single instance of the DbContext, remove the call to Context.Dispose(). This would fix the problem you're getting right now, but it will likely introduce other problems in the future.
But I would strongly caution against using a single DbContext in a scenario where multiple threads could be trying to access it simultaneously. According to the DbContext specs:
Any instance members are not guaranteed to be thread safe.
You'd be better off just removing the static keyword from your field.