eXpandFramework / eXpand

DevExpress XAF (eXpressApp) extension framework. 𝗹𝗶𝗻𝗸𝗲𝗱𝗶𝗻.𝗲𝘅𝗽𝗮𝗻𝗱𝗳𝗿𝗮𝗺𝗲𝘄𝗼𝗿𝗸.𝗰𝗼𝗺, 𝘆𝗼𝘂𝘁𝘂𝗯𝗲.𝗲𝘅𝗽𝗮𝗻𝗱𝗳𝗿𝗮𝗺𝗲𝘄𝗼𝗿𝗸.𝗰𝗼𝗺 and 𝘁𝘄𝗶𝘁𝘁𝗲𝗿 @𝗲𝘅𝗽𝗮𝗻𝗱𝗳𝗿𝗮𝗺𝗲𝘄𝗼𝗿𝗸 and or simply 𝗦𝘁𝗮𝗿/𝘄𝗮𝘁𝗰𝗵 this repository and get notified from 𝗚𝗶𝘁𝗛𝘂𝗯
http://expand.expandframework.com
Microsoft Public License
222 stars 115 forks source link

21.1.3 - Cannot start app due to Suequence Generator #871

Closed isatahiri closed 3 years ago

isatahiri commented 3 years ago

With the new prelease 4.211.0.5, I cannot start the app if I add the sequence generator in the required module : RequiredModuleTypes.Add(typeof(Xpand.XAF.Modules.SequenceGenerator.SequenceGeneratorModule));

Note that i'm using the connection string is given on the fly on the OnLoggingOn. This was working in the 20.2.7. What has changed ?

` he error occurred:

Type:       UnableToOpenDatabaseException
Message:    Impossible d'ouvrir la base de données. Chaîne de connexion : 'server=XXXXX;user id=root;password=***REMOVED***;database=XXXXX;persistsecurityinfo=True;characterset=utf8;'; Erreur : 'MySql.Data.MySqlClient.MySqlException (0x80004005): Unable to connect to any of the specified MySQL hosts.

---> System.AggregateException: One or more errors occurred. (Hôte inconnu.) ---> System.Net.Sockets.SocketException (11001): Hôte inconnu. at System.Net.NameResolutionPal.ProcessResult(SocketError errorCode, GetAddrInfoExContext context) at System.Net.NameResolutionPal.GetAddressInfoExCallback(Int32 error, Int32 bytes, NativeOverlapped overlapped) --- End of stack trace from previous location ---

--- End of inner exception stack trace --- at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) at System.Threading.Tasks.Task.Wait() at MySql.Data.Common.StreamCreator.GetTcpStream(MySqlConnectionStringBuilder settings, MyNetworkStream& networkStream) at MySql.Data.Common.StreamCreator.GetStream(MySqlConnectionStringBuilder settings, MyNetworkStream& networkStream) at MySql.Data.MySqlClient.NativeDriver.Open() at MySql.Data.MySqlClient.NativeDriver.Open() at MySql.Data.MySqlClient.Driver.Open() at MySql.Data.MySqlClient.Driver.Create(MySqlConnectionStringBuilder settings) at MySql.Data.MySqlClient.MySqlPool.CreateNewPooledConnection() at MySql.Data.MySqlClient.MySqlPool.GetPooledConnection() at MySql.Data.MySqlClient.MySqlPool.TryToGetDriver() at MySql.Data.MySqlClient.MySqlPool.GetConnection() at MySql.Data.MySqlClient.MySqlConnection.Open() at DevExpress.Xpo.DB.MySqlConnectionProvider.CreateDataBase()' Data: 0 entries Stack trace:

at DevExpress.Xpo.DB.MySqlConnectionProvider.CreateDataBase() at DevExpress.Xpo.DB.ConnectionProviderSql..ctor(IDbConnection connection, AutoCreateOption autoCreateOption) at DevExpress.Xpo.DB.MySqlConnectionProvider..ctor(IDbConnection connection, AutoCreateOption autoCreateOption) at DevExpress.Xpo.DB.MySqlConnectionProvider.CreateProviderFromConnection(IDbConnection connection, AutoCreateOption autoCreateOption) at DevExpress.Xpo.DB.MySqlConnectionProvider.CreateProviderFromString(String connectionString, AutoCreateOption autoCreateOption, IDisposable[]& objectsToDisposeOnDisconnect) at DevExpress.Xpo.DB.DataStoreBase.QueryDataStore(String providerType, String connectionString, AutoCreateOption defaultAutoCreateOption, IDisposable[]& objectsToDisposeOnDisconnect) at DevExpress.Xpo.XpoDefault.GetConnectionProvider(String connectionString, AutoCreateOption defaultAutoCreateOption, IDisposable[]& objectsToDisposeOnDisconnect) at DevExpress.Xpo.XpoDefault.GetDataLayer(String connectionString, XPDictionary dictionary, AutoCreateOption defaultAutoCreateOption, IDisposable[]& objectsToDisposeOnDisconnect) at DevExpress.Xpo.XpoDefault.GetDataLayer(String connectionString, XPDictionary dictionary, AutoCreateOption defaultAutoCreateOption) at Xpand.XAF.Modules.SequenceGenerator.SequenceGeneratorService.<>c__DisplayClass37_0.b_0() at System.Reactive.Linq.ObservableImpl.Defer`1..Run() in /_/Rx.NET/Source/src/System.Reactive/Linq/Observable/Defer.cs:line 37

InnerException:

        Type:       MySqlException
        Message:    Unable to connect to any of the specified MySQL hosts.
        Data:       1 entries
                'Server Error Code'     '1042'
        Stack trace:

at MySql.Data.MySqlClient.NativeDriver.Open() at MySql.Data.MySqlClient.Driver.Open() at MySql.Data.MySqlClient.Driver.Create(MySqlConnectionStringBuilder settings) at MySql.Data.MySqlClient.MySqlPool.CreateNewPooledConnection() at MySql.Data.MySqlClient.MySqlPool.GetPooledConnection() at MySql.Data.MySqlClient.MySqlPool.TryToGetDriver() at MySql.Data.MySqlClient.MySqlPool.GetConnection() at MySql.Data.MySqlClient.MySqlConnection.Open() at DevExpress.Xpo.DB.MySqlConnectionProvider.CreateDataBase()

        InnerException:

                Type:       AggregateException
                Message:    One or more errors occurred. (Hôte inconnu.)
                Data:       0 entries
                Stack trace:

at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) at System.Threading.Tasks.Task.Wait() at MySql.Data.Common.StreamCreator.GetTcpStream(MySqlConnectionStringBuilder settings, MyNetworkStream& networkStream) at MySql.Data.Common.StreamCreator.GetStream(MySqlConnectionStringBuilder settings, MyNetworkStream& networkStream) at MySql.Data.MySqlClient.NativeDriver.Open()

                InnerException:

                        Type:       SocketException
                        Message:    Hôte inconnu.
                        Data:       0 entries
                        Stack trace:

at System.Net.NameResolutionPal.ProcessResult(SocketError errorCode, GetAddrInfoExContext context) at System.Net.NameResolutionPal.GetAddressInfoExCallback(Int32 error, Int32 bytes, NativeOverlapped overlapped) --- End of stack trace from previous location ---

                        InnerException is null

`

apobekiaris commented 3 years ago

UnableToOpenDatabaseException

This tell us that everything up to there, assembly loading etc. went well, but there is a connectivity problem. Without the module starts?

isatahiri commented 3 years ago

I agree. But if i remove : RequiredModuleTypes.Add(typeof(Xpand.XAF.Modules.SequenceGenerator.SequenceGeneratorModule)); from the module.cs, then I can connect to the app.

So that means that the sequence generator uses the CS before the logging on ? Or it uses the CS in the configuration file and not the ObjectSpace ?

isatahiri commented 3 years ago

I do not see any change in this Expand module since 30.01.2021 according to Github history... So i'm a bit lost. How can this work in 20.2.7 and not in 21.1.3... Must be a DX breaking change that I missed.

apobekiaris commented 3 years ago

at DevExpress.Xpo.DB.MySqlConnectionProvider.CreateDataBase()

hmmm, hard to tell. The module uses the Xpand.XAF.Modules.SequenceGenerator.Extensions.GetConnectionString method can u see its value OnBeForeLogin?

apobekiaris commented 3 years ago

I do not see any change in this Expand module

yeah but there are dependecies so u really not know unless u have tests which again are only indicators

isatahiri commented 3 years ago

Well I have not tested but I do not expect to see the CS on OnBeForeLogin as I set the CS on OnLoggingOn. The method that you mentioned gets the CS of an objectSpaceProvider so it this is done after the login, there no reason to fail.

Maybe something is wrong with my method here ?

protected override void OnLoggingOn(LogonEventArgs args) { base.OnLoggingOn(args); try { IConfiguration configuration = ServiceProvider.GetRequiredService<IConfiguration>(); var adminCS = configuration.GetConnectionString("AdminConnectionString"); var brokerAdminDal = XpoDefault.GetDataLayer(adminCS, DevExpress.Xpo.DB.AutoCreateOption.None); using (var baUow = new UnitOfWork(brokerAdminDal)) { var username = ((AuthenticationStandardLogonParameters)args.LogonParameters).UserName; BrokeroAdmin.Module.BO.TenantAccess tenantAccess = baUow.Query<BrokeroAdmin.Module.BO.TenantAccess>() .Where(ta => ta.Access.UserAccess == username) .First(); if (tenantAccess != null) { ((XPObjectSpaceProvider)ObjectSpaceProviders[0]).SetDataStoreProvider(GetDataStoreProvider(tenantAccess.Tenant.ConnectionString, null)); } } } catch { throw new UserFriendlyException($"Nom d'utilsateur ou mot de passe inconnu. Veuillez vérifier les données entrées ou appeler le support technique."); } }

Then this become a real blocking point for me.

isatahiri commented 3 years ago

Any suggestion would be very welcome! Do u need a sample project?

apobekiaris commented 3 years ago

        internal static IObservable<Unit> Connect(this ApplicationModulesManager manager,Type sequenceStorageType=null){
            sequenceStorageType ??= typeof(SequenceStorage);
            Guard.TypeArgumentIs(typeof(ISequenceStorage),sequenceStorageType,nameof(sequenceStorageType));
            return manager.WhenApplication(application => application.WhenCompatibilityChecked().FirstAsync().Select(xafApplication => xafApplication.ObjectSpaceProvider)
                .Where(provider => !provider.IsInstanceOf("DevExpress.ExpressApp.Security.ClientServer.MiddleTierServerObjectSpaceProvider"))
                .SelectMany(provider => provider.SequenceGeneratorDatalayer()
                    .SelectMany(dataLayer => application.WhenObjectSpaceCreated().GenerateSequences(dataLayer,sequenceStorageType)
                        .Merge(application.Security.AddAnonymousType(sequenceStorageType).ToObservable()))
                    .Merge(application.ConfigureDetailViewSequenceStorage()).ToUnit()));
        }

according to the source code SequenceGenerator.Connect method the SequenceGenerator Layer is contructed after the First WhenCompatibilityChecked. For some reason this method is called (it shouldn't) before the correct CS is assigned

isatahiri commented 3 years ago

I created a sample project where i give the cs on the OnLoggingOn. I hope this will help. TestNet5.zip

apobekiaris commented 3 years ago

hmm there is some confusion here, y9ou need to find out which connectionstring is used from Sequencegenetator. The modules as I mentioed subsrbes to the sequence generated after the first ObjectSpace is created actually after the CompatibilityChecked. This by design happens after login, in your solution something is diffenrent so i suggested to override the Login and check the value of s.

var s = ((XPObjectSpaceProvider) ObjectSpaceProviders[0]).GetConnectionString();

hope i make more sense now

isatahiri commented 3 years ago

After some research this seems to be coming from here :

The pooling was not activated. I'm not sure of what it means but after enabling this it works in the sample. Now this does not work in my solution... The app starts but when i create a record, it does not increment the sequencial number. I need to find why.

apobekiaris commented 3 years ago

remember the module uses an ExplicitUnitOfWork maybe your Updaters ??

isatahiri commented 3 years ago

In the there is nothing related to the SequenceGenerator module in both solution. Here is the diffs in ObjectSpaceProviders between the sample and my app. Nothing wrong here.. Both type are supported i guess.

image

apobekiaris commented 3 years ago
  1. What we know is that the application.CheckCompatibility method is called cause perhaps an ObjectSapce created
  2. As a result the XpoDefault.GetDataLayer(objectSpaceProvider.GetConnectionString()) was called leading to the exception

maybe calling this XpoDefault.GetDataLayer(objectSpaceProvider.GetConnectionString()) at the appropriate place depending on what logic u have in your create first object space area (by default after login)

apobekiaris commented 3 years ago

maybe calling this XpoDefault.GetDataLayer(objectSpaceProvider.GetConnectionString())

calling it in order to try to understand the problem not as a fix, it should throw there in place where u can examine the objectSpaceProvider.GetConnectionString() and can see the problem.

apobekiaris commented 3 years ago

having DX symbols in place I would set a breakpoint in XafApplication.CheckCombatibility and see who makes the first caLL that results in the exception

isatahiri commented 3 years ago

hmm. Not sure I understand this Tolis. I mean I understand but not sure i'm able to analyse that deep by myself.

So as info, the migration of my app to 21.1.3 was a nightmare because of migration to .net 5. So what I did last week was :

I seems that some code was missing/forgotten which I added right now : image

With this, it seems to work now.

Note that with this the app is much slower as it is goes in getter and setter many many time. image

isatahiri commented 3 years ago

This peace of code is I guess from the first implementation of the Sequence Generator. It does not look very clean does it ?

apobekiaris commented 3 years ago

i do not recognize this code try to repro it a sample by adding parts as u did b4, start first from the SequenceGen module which works in a sample app then add incrementally your components as u did before until you find what to blame. You know the drill, it always works

isatahiri commented 3 years ago

So i did create a new sample using the SecuredObjectSpaceProvider instead of XPObjectSpaceProvider and i confirm that without adding this in the BlazorApplication.cs it does not work : private static ConcurrentDictionary<string, bool> isCompatibilityChecked = new ConcurrentDictionary<string, bool>(); protected override bool IsCompatibilityChecked { get => isCompatibilityChecked.GetOrAdd(ConnectionString, false); set => isCompatibilityChecked.TryAdd(ConnectionString, value); }

DXApplication3.zip

isatahiri commented 3 years ago

Can this be related the the problematic that Dennis is exposed some years ago ?

apobekiaris commented 3 years ago

where did u find the snippet? r u in middletier?

isatahiri commented 3 years ago

I'm not in middletier... When you create a Blazor app with standard authentification, then you ObjectSpaceProvider is DevExpress.ExpressApp.Security.ClientServer.SecuredSessionObjectLayer. If you create a Blazor app without authentification, then your ObjectSpaceProvider is XPObjectSpaceProvider.

In the new sample, if you are running with DevExpress.ExpressApp.Security.ClientServer.SecuredSessionObjectLayer the app runs but does not increment.

isatahiri commented 3 years ago

In the method WhenSupported which seems the be used by the Generate sequence, do this check :

if (space.UnitOfWork().DataLayer is BaseDataLayer dataLayer)

not sure what it means ? is this related somehow ?

apobekiaris commented 3 years ago

the last sample works fine in my side, had to comment out the

string connectionsString = @"Integrated Security=SSPI;Pooling=false;Data Source=(localdb)\mssqllocaldb;Initial Catalog=DXApplication3";
                ((XPObjectSpaceProvider)ObjectSpaceProviders[0]).SetDataStoreProvider(GetDataStoreProvider(connectionsString, null));

cause I do not see why it is used

isatahiri commented 3 years ago

Sorry Tolis i had a typo issue in my example.

Please past the OnLoggingOn by this : protected override void OnLoggingOn(LogonEventArgs args) { base.OnLoggingOn(args); try { string connectionsString = @"Integrated Security=SSPI;Pooling=false;Data Source=(localdb)\mssqllocaldb;Initial Catalog=DXApplication3"; ((XPObjectSpaceProvider)ObjectSpaceProviders[0]).SetDataStoreProvider(GetDataStoreProvider(connectionsString, null)); } catch { throw new UserFriendlyException($"Nom d'utilsateur ou mot de passe inconnu. Veuillez vérifier les données entrées ou appeler le support technique."); } }

And if you comment this part : private static ConcurrentDictionary<string, bool> isCompatibilityChecked = new ConcurrentDictionary<string, bool>(); protected override bool IsCompatibilityChecked { get => isCompatibilityChecked.GetOrAdd(ConnectionString, false); set => isCompatibilityChecked.TryAdd(ConnectionString, value); }

After this you will see that the increment does not work.

isatahiri commented 3 years ago

cause I do not see why it is used

The point of this to set the CS in the OnLoggingOn after the user enters his credentials.

apobekiaris commented 3 years ago

I am asking u again, where did u find this line?

private static ConcurrentDictionary<string, bool> isCompatibilityChecked = new ConcurrentDictionary<string, bool>(); protected override bool IsCompatibilityChecked { get => isCompatibilityChecked.GetOrAdd(ConnectionString, false); set => isCompatibilityChecked.TryAdd(ConnectionString, value); }
isatahiri commented 3 years ago

Sorry for the late answer. I researched and was wrong this line is coming from the DX sample

Will have a look and give more info on this issue later.