Base solution for your next web application
Open Closed

Zero fails to start, requires EF Core MultiTenantMigrateExecuter to run earlier than PreInitialize #6598


User avatar
0
peabaw created

We have the following code in EntityFrameworkCoreModule.cs:

public override void PostInitialize()
		{
			var configurationAccessor = IocManager.Resolve<IAppConfigurationAccessor>();
			EnsureMigrated();
			using (var scope = IocManager.CreateScope())
			{
				if (!SkipDbSeed && scope.Resolve<DatabaseCheckHelper>().Exist(configurationAccessor.Configuration["ConnectionStrings:Default"]))
					SeedHelper.SeedHostDb(IocManager);
			}
		}

		private void EnsureMigrated()
		{
			using (var migrateExecuter = IocManager.ResolveAsDisposable<MultiTenantMigrateExecuter>())
			{
				migrateExecuter.Object.Run();
			}
		}

The MultiTenantMigrateExecuter.Run() performs an AbpZeroDbMigrator.CreateOrMigrateForHost(SeedHelper.SeedHostDb).

This has worked fine. However after updrading to the latest Zero version 6.7.0 we have stumbled into some problems.

The API stops at boot with the following error: SqlException: Invalid column name 'SubscriptionPaymentType'.

This happens before the migration occurs. So it seems the boot process requires the migration to be run before boot and PostInitialize is then too late.

Running a manual database update work, but this makes out CI/CD in Azure Devops to stop working.

Any ideas how to make the migration happen before the startup boot?


9 Answer(s)
  • User Avatar
    0
    ryancyq created
    Support Team

    Hi, can you share if you are using .net framework or .net core?

    Also can you share the full stacktrace of the SqlException?

  • User Avatar
    0
    peabaw created

    Hi.

    Core version 2.2 (Angular+API)

    System.Data.SqlClient.SqlException (0x80131904): Invalid column name 'SubscriptionPaymentType'.
       at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
       at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
       at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
       at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
       at System.Data.SqlClient.SqlDataReader.get_MetaData()
       at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
       at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds)
       at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior)
       at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.Execute(IRelationalConnection connection, DbCommandMethod executeMethod, IReadOnlyDictionary`2 parameterValues)
       at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteReader(IRelationalConnection connection, IReadOnlyDictionary`2 parameterValues)
       at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.BufferlessMoveNext(DbContext _, Boolean buffer)
       at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
       at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.MoveNext()
       at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Boolean& found)
       at lambda_method(Closure )
       at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ResultEnumerable`1.GetEnumerator()
       at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider._TrackEntities[TOut,TIn](IEnumerable`1 results, QueryContext queryContext, IList`1 entityTrackingInfos, IList`1 entityAccessors)+MoveNext()
       at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
       at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Boolean& found)
       at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
       at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.&lt;&gt;c__DisplayClass15_1`1.<CompileQueryCore>b__0(QueryContext qc)
       at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source, Expression`1 predicate)
       at Castle.Proxies.Invocations.IRepository`2_FirstOrDefault.InvokeMethodOnTarget()
       at Castle.DynamicProxy.AbstractInvocation.Proceed()
       at Abp.Domain.Uow.UnitOfWorkInterceptor.PerformSyncUow(IInvocation invocation, UnitOfWorkOptions options)
       at Castle.DynamicProxy.AbstractInvocation.Proceed()
       at Castle.Proxies.IRepository`1Proxy.FirstOrDefault(Int32 id)
       at Abp.MultiTenancy.TenantCache`2.GetTenantOrNull(Int32 tenantId)
       at Castle.Proxies.Invocations.TenantCache`2_GetTenantOrNull.InvokeMethodOnTarget()
       at Castle.DynamicProxy.AbstractInvocation.Proceed()
       at Abp.Domain.Uow.UnitOfWorkInterceptor.PerformSyncUow(IInvocation invocation, UnitOfWorkOptions options)
       at Castle.DynamicProxy.AbstractInvocation.Proceed()
       at Castle.Proxies.TenantCache`2Proxy.GetTenantOrNull(Int32 tenantId)
       at Abp.MultiTenancy.TenantCache`2.<>c__DisplayClass7_0.<GetOrNull>b__0()
       at Abp.Runtime.Caching.CacheExtensions.<>c__DisplayClass5_0`2.&lt;Get&gt;b__0(String k)
       at Abp.Runtime.Caching.CacheBase.Get(String key, Func`2 factory)
       at Abp.Runtime.Caching.CacheExtensions.Get[TKey,TValue](ICache cache, TKey key, Func`2 factory)
       at Castle.Proxies.Invocations.ITenantCache_GetOrNull.InvokeMethodOnTarget()
       at Castle.DynamicProxy.AbstractInvocation.Proceed()
       at Castle.DynamicProxy.AbstractInvocation.Proceed()
       at Castle.Proxies.TenantCache`2Proxy.GetOrNull(Int32 tenantId)
       at Abp.MultiTenancy.TenantStore.Find(Int32 tenantId)
       at Abp.Configuration.SettingManager.<>c__DisplayClass41_0.<<GetTenantSettingsFromCache>b__0>d.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
       at Abp.Runtime.Caching.CacheExtensions.<>c__DisplayClass9_0`2.&lt;&lt;GetAsync&gt;b__0>d.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
       at Abp.Runtime.Caching.CacheBase.GetAsync(String key, Func`2 factory)
       at Abp.Runtime.Caching.CacheExtensions.GetAsync[TKey,TValue](ICache cache, TKey key, Func`2 factory)
       at Abp.Configuration.SettingManager.GetTenantSettingsFromCache(Int32 tenantId)
       at Abp.Configuration.SettingManager.GetReadOnlyTenantSettings(Int32 tenantId)
       at Abp.Configuration.SettingManager.GetSettingValueForTenantOrNullAsync(Int32 tenantId, String name)
       at Abp.Configuration.SettingManager.GetSettingValueInternalAsync(String name, Nullable`1 tenantId, Nullable`1 userId, Boolean fallbackToDefault)
       at Abp.Configuration.SettingManagerExtensions.GetSettingValueAsync[T](ISettingManager settingManager, String name)
       at Nito.AsyncEx.Synchronous.TaskExtensions.WaitAndUnwrapException[TResult](Task`1 task)
       at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
    --- End of stack trace from previous location where exception was thrown ---
       at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
    --- End of stack trace from previous location where exception was thrown ---
       at Nito.AsyncEx.Synchronous.TaskExtensions.WaitAndUnwrapException[TResult](Task`1 task)
       at Nito.AsyncEx.AsyncContext.Run[TResult](Func`1 action)
       at Integration.GHRIntegrationModule.PostInitialize() in F:\a\1\s\aspnet-core\src\Integration\GHRIntegrationModule.cs:line 73
       at System.Collections.Generic.List`1.ForEach(Action`1 action)
       at Abp.AbpBootstrapper.Initialize()
       at Abp.AspNetCore.AbpApplicationBuilderExtensions.InitializeAbp(IApplicationBuilder app)
       at Abp.AspNetCore.AbpApplicationBuilderExtensions.UseAbp(IApplicationBuilder app, Action`1 optionsAction)
       at Web.Startup.Startup.Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) in F:\a\1\s\aspnet-core\src\Web.Host\Startup\Startup.cs:line 154
    --- End of stack trace from previous location where exception was thrown ---
    
  • User Avatar
    0
    peabaw created

    In the stack trace there is a reference to GHRIntegrationModule.PostInitialize. I wasa afraid that the module was loaded in the wrong order, but adding [DependsOn(typeof(EntityFrameworkCoreModule))] did not change anything. The exception still occurs and the stack trace remains the same.

    The mentioned line in the PostInitialize (row 73) is the following: var activeDirectoryServerName = configurationAccessor.Configuration["Integration:ActiveDirectoryServerName"]; I fail to see how that line would affect EF though.

  • User Avatar
    0
    ryancyq created
    Support Team

    Can you share the code in GHRIntegrationModule/PostInitialize() which calls SettingManager.GetSettingValueAsync()

    Also you might want to consider moving EnsureMigrated() into YourProjectEntityFrameworkCoreModule

  • User Avatar
    0
    peabaw created

    GHRIntegrationModule:

    public override void PostInitialize()
            {
                var configurationAccessor = IocManager.Resolve<IAppConfigurationAccessor>();
    
                #region Integration:ActiveDirectoryServer
    
                var activeDirectoryServerName = configurationAccessor.Configuration["Integration:ActiveDirectoryServerName"];
    
                SettingManager settingsManager = IocManager.Resolve<SettingManager>();
                if (settingsManager.GetSettingValue<bool>(LdapSettingNames.IsEnabled))
                {
                    // Use AD reader
                    IocManager.Register<IActiveDirectoryReader, ActiveDirectoryReader>();
                    var activeDirectoryReader = Configuration.Get<ActiveDirectoryReader>();
                    activeDirectoryReader.ActiveDirectoryServerName = activeDirectoryServerName;
                }
                else
                {
                    // Use fake AD search
                    IocManager.Register<IActiveDirectoryReader, FakeActiveDirectoryReader>();
                }
    
                #endregion Integration:ActiveDirectoryServer
    
                IocManager.Release(configurationAccessor);
            }
    

    I shortened the name fot the file (EntityFrameworkCoreModule), the file is named GHREntityFrameworkCoreModule.cs so I guess it is the same file you mentioned.

  • User Avatar
    0
    peabaw created

    I solved it. I had to add Depends on, but I added it the wrong way it seems,

    I added it twice and this did not seem to work: [DependsOn(typeof(AbpAspNetCoreModule))] [DependsOn(typeof(GHREntityFrameworkCoreModule))]

    Working way: [DependsOn(typeof(AbpAspNetCoreModule), typeof(GHREntityFrameworkCoreModule))]

    It still caused an error but the site is now working so I guess the mentioned index does not pose an error:

    [INFO ] 2019-03-06 16:05:54,165 [1 ] meworkCore.GHRMultiTenantMigrateExecuter - HOST database migration started... [ERROR] 2019-03-06 16:05:55,837 [1 ] meworkCore.GHRMultiTenantMigrateExecuter - Canceled migrations - An error occured during migration of host database. System.Data.SqlClient.SqlException (0x80131904): Cannot release index AppSubscriptionPayments.IX_AppSubscriptionPayments_PaymentId_Gateway, because the object does not exist or you do not have necessary rights. at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action1 wrapCloseInAction) at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose) at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout, Boolean asyncWrite) at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource1 completion, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite, String methodName) at System.Data.SqlClient.SqlCommand.ExecuteNonQuery() at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.Execute(IRelationalConnection connection, DbCommandMethod executeMethod, IReadOnlyDictionary2 parameterValues) at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteNonQuery(IRelationalConnection connection, IReadOnlyDictionary2 parameterValues) at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationCommandExecutor.ExecuteNonQuery(IEnumerable1 migrationCommands, IRelationalConnection connection) at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(String targetMigration) at Abp.Zero.EntityFrameworkCore.AbpZeroDbMigrator1.CreateOrMigrate(AbpTenantBase tenant, Action`1 seedAction) at EntityFrameworkCore.GHRMultiTenantMigrateExecuter.Run() ClientConnectionId:044c12ef-ea29-4054-ba90-a7d5839fa7c1 Error Number:3701,State:7,Class:11

  • User Avatar
    0
    ryancyq created
    Support Team

    It seems that the you are trying to store LdapSettingNames.IsEnabled for each tenant and load it during module initialization.

    This approach is incorrect as module initialization shouldn't have tenan/user concept. The more appropriate way is to get it from configuraiton.

    configurationAccessor.Configuration["Integration:IsActiveDirectoryEnabled"];
    
  • User Avatar
    0
    peabaw created

    What I'm actually trying to do is to check if LDAP is enabled for the system (not tenant, it is not a multi tenant system). If it is we will use the "real" AD Search. If not (i.e. on a development machine) we use a fake AD Search.

    Is there a better way to check if the LDAP Setting has been enabled (as it is set/enabled in GUI)?

  • User Avatar
    0
    ryancyq created
    Support Team

    You can inject IAbpZeroLdapModuleConfig (for system) or ILdapSettings(for tenant) to determind if LDAP is enabled.

    ANZ uses ABP framework's AbpZeroLdapModule to implement LDAP authentication.

    See ANZ implemenation at https://github.com/aspnetzero/aspnet-zero-core/blob/dev/aspnet-core/src/MyCompanyName.AbpZeroTemplate.Core/Authorization/Ldap/AppLdapAuthenticationSource.cs