EPiServer.Marketing.Testing breaks read-only database mode

Vote:
 

We've noticed that our CMS 12 implementation fails the read-only zero downtime mode deployments in the DXP. After enabling the read-only database mode locally (with the setting: "EPiServer:Cms:DataAccess:DatabaseMode": "ReadOnly"), we got a startup error with the following error "Automatic database schema update is prevented since the application is set to ReadOnly mode.".  We didn't update just now and had it running before the config change.

After looking into the trace logs, we noticed that the A/B testing addon executes 8 SQL scripts at every startup. Removing the EPiServer.Marketing.Testing package resolves the error at startup in read-only mode. Doing a dotnet new Alloy and adding the A/B testing addon and starting in read-only mode (after doing a normal start to let it update) reproduces the issue. All packages up to date and on net6. The exception log below is from this reproduction.

So it looks like the A/B testing module improperly checks the database on whether it should run update scripts or not and triggers the exception. 

  alloy12    dotnet run
info: EPiServer.Framework.Initialization.InitializationEngine[0]
Initialization started
warn: Microsoft.EntityFrameworkCore.Model.Validation[30000]
No store type was specified for the decimal property 'ConvertedTotal' on entity type 'DalKeyFinancialResult'. This will cause values to be silently truncated if they do not fit in the de
fault precision and scale. Explicitly specify the SQL server column type that can accommodate all the values in 'OnModelCreating' using 'HasColumnType', specify precision and scale using 'HasP
recision', or configure a value converter using 'HasConversion'.
warn: Microsoft.EntityFrameworkCore.Model.Validation[30000]
No store type was specified for the decimal property 'Total' on entity type 'DalKeyFinancialResult'. This will cause values to be silently truncated if they do not fit in the default pre
cision and scale. Explicitly specify the SQL server column type that can accommodate all the values in 'OnModelCreating' using 'HasColumnType', specify precision and scale using 'HasPrecision'
, or configure a value converter using 'HasConversion'.
fail: EPiServer.Framework.Initialization.InitializationEngine[0]
Initialize action failed for 'Initialize on class EPiServer.Data.DataInitialization, EPiServer.Data, Version=12.8.0.0, Culture=neutral, PublicKeyToken=8fe83dea738b45b7'
System.NotSupportedException: Automatic database schema update is prevented since the application is set to ReadOnly mode. The mode can be changed by setting DataAccesssOptions.DatabaseM
ode.
at EPiServer.Data.SchemaUpdates.Internal.DatabaseSchemaManagerImplementation.ThrowDatabaseModeReadOnlyException()
at EPiServer.Data.SchemaUpdates.Internal.DatabaseSchemaManagerImplementation.EnsureDatabaseSchema(DataAccessOptions dataAccessOptions)
at EPiServer.Data.DataInitialization.ValidateDatabaseSchema(InitializationEngine context)
at EPiServer.Data.DataInitialization.Initialize(InitializationEngine context)
at EPiServer.Framework.Initialization.Internal.ModuleNode.<>c__DisplayClass4_0.<Initialize>b__0()
at EPiServer.Framework.Initialization.Internal.ModuleNode.Execute(Action a, String key)
at EPiServer.Framework.Initialization.Internal.ModuleNode.Initialize(InitializationEngine context)
at EPiServer.Framework.Initialization.InitializationEngine.InitializeModules()
System.NotSupportedException: Automatic database schema update is prevented since the application is set to ReadOnly mode. The mode can be changed by setting DataAccesssOptions.DatabaseM
ode.
at EPiServer.Data.SchemaUpdates.Internal.DatabaseSchemaManagerImplementation.ThrowDatabaseModeReadOnlyException()
at EPiServer.Data.SchemaUpdates.Internal.DatabaseSchemaManagerImplementation.EnsureDatabaseSchema(DataAccessOptions dataAccessOptions)
at EPiServer.Data.DataInitialization.ValidateDatabaseSchema(InitializationEngine context)
at EPiServer.Data.DataInitialization.Initialize(InitializationEngine context)
at EPiServer.Framework.Initialization.Internal.ModuleNode.<>c__DisplayClass4_0.<Initialize>b__0()
at EPiServer.Framework.Initialization.Internal.ModuleNode.Execute(Action a, String key)
at EPiServer.Framework.Initialization.Internal.ModuleNode.Initialize(InitializationEngine context)
at EPiServer.Framework.Initialization.InitializationEngine.InitializeModules()
Unhandled exception. EPiServer.Framework.Initialization.InitializationException: Initialize action failed for Initialize on class EPiServer.Data.DataInitialization, EPiServer.Data, Version=12.
8.0.0, Culture=neutral, PublicKeyToken=8fe83dea738b45b7
---> System.NotSupportedException: Automatic database schema update is prevented since the application is set to ReadOnly mode. The mode can be changed by setting DataAccesssOptions.DatabaseM
ode.
at EPiServer.Data.SchemaUpdates.Internal.DatabaseSchemaManagerImplementation.ThrowDatabaseModeReadOnlyException()
at EPiServer.Data.SchemaUpdates.Internal.DatabaseSchemaManagerImplementation.EnsureDatabaseSchema(DataAccessOptions dataAccessOptions)
at EPiServer.Data.DataInitialization.ValidateDatabaseSchema(InitializationEngine context)
at EPiServer.Data.DataInitialization.Initialize(InitializationEngine context)
at EPiServer.Framework.Initialization.Internal.ModuleNode.<>c__DisplayClass4_0.<Initialize>b__0()
at EPiServer.Framework.Initialization.Internal.ModuleNode.Execute(Action a, String key)
at EPiServer.Framework.Initialization.Internal.ModuleNode.Initialize(InitializationEngine context)
at EPiServer.Framework.Initialization.InitializationEngine.InitializeModules()
--- End of inner exception stack trace ---
at EPiServer.Framework.Initialization.InitializationEngine.InitializeModules()
at EPiServer.Framework.Initialization.InitializationEngine.ExecuteTransition(Boolean continueTransitions)
at EPiServer.Framework.Initialization.InitializationEngine.Initialize()
at EPiServer.Hosting.Internal.EPiServerFrameworkHost.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
at alloy12.Program.Main(String[] args) in C:\Dev\Experiments\alloy12\Program.cs:line 5

#285617
Aug 16, 2022 14:19
Vote:
 

Thanks for the deatiled information.  This package is now open source and I added a PR to fix the issue of running migrations everytime.  You can test out the version here.

As for the other error you will need to disable automatice shema updates.

"EPiServer": {
        "CMS": {
          "clientResource": {
            "Debug": "true"
          },
          "dataAccess": {
            "databaseMode": "ReadOnly",
            "UpdateDatabaseSchema": false
          }
        },
}
#285673
Aug 16, 2022 23:01
Vote:
 

Thanks for the quick response!

I've used the package EPiServer.Marketing.Testing with version 3.1.1-ci-87 and the problem is gone in the reproduction. We don't need to disable the automatic schema updates with this fix.

However, for our real CMS implementation (upgrade from CMS 11) we are still seeing the problem. Looking at the trace logs we see that the package/namepace EPiServer.Marketing.KPI is executing an SQL script at each startup (see log below). The 8 other SQL script executions are gone now, though. Those were from the EPiServer.Marketing.Testing.Web assembly/namepace.

dbug: EPiServer.Data.SchemaUpdates.Internal.ScriptExecutorImplementation[0]
      Execute Script: ''1.0.0.1.sql', 'EPiServer.Marketing.KPI, Version=3.1.1.0, Culture=neutral, PublicKeyToken=null', 'EPiServer.Marketing.KPI.SchemaUpdater.Kpi.zip'' 
trce: EPiServer.Data.Internal.MARSConnectionContextHandler[0]
      Using existing transaction

The reproduction doesn't execute this SQL script at startup. So there is something different between them. 

#285685
Aug 17, 2022 13:06
Vote:
 

I've been looking at the source code and I think I have an idea of what's going wrong here. The logs point to a ScemaUpdater in the EPiServer.Marketing.KPI project. This does a call to GetDatabaseVersion with a Scema and a ContextKey, when I navigate the symbols down to the BaseRepository the ContextKey in the GetDatabaseVersion(string contextKey) doesn't get used. So takes the last version in the __MigrationHistory table, which likely is the wrong one. 

#285690
Aug 17, 2022 15:07
Vote:
 

Yes sorry did not realize there were two schema updaters.  I pushed changes to the pull request and there is a new package version 3.1.1-ci-88, can you try that one.

#285691
Aug 17, 2022 15:44
Vote:
 

Yes, the problem is now gone with the latest update. We can start our CMS 12 solution in read only mode while leaving the automatic database schema updaterer on.

I'm still wondering though whether the .LastOrDefault() is the correct solution on it's own. Currently the GetDatabaseVersion method just takes the last record on the __EFMigrationsHistory table, but this record may be from DbLocalizationProvider or something else, now it uses the number from the wrong migration to compare to whether do the migrations which might cause it to skip the migrations altogether on new installs. The CMS 11 version of this method has a .Where() to filter on the context key. I might be missing something here but it feels like it's still bugged although it's not causing an error at the moment. 

#285692
Aug 17, 2022 17:27
Vote:
 

Yeah I will update both to take into account context key.

#285693
Aug 17, 2022 18:01
Vote:
 

I updated the PR to use te context key.  If you dont mind testing one more time.  I will put 3.1.1 on the feed on on relase Tuesday if this solves all the issues.

#285694
Aug 17, 2022 19:15
Vote:
 

I updated both the reproduction and the main solution to 3.1.1-ci-89, tested both with and without read-only mode enabled and I'm seeing no problems so far. It looks fixed from my end. 👍

#285696
Aug 17, 2022 19:35
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.