I am upgrading multiple companies to NAV 2016. I really like to use the multi tenant setup and use it in most cases.
In NAV we have the option to make a table common with all companies.
This option has been available for all the versions of NAV that I can remember.
Using a multi tenant setup means that you have a dedicated database for each tenant and normally only one company for each tenant. That makes this option completely useless.
I was running this same multi tenant setup in NAV 2013 R2 and there I solved this issue by modifying the table to be a linked table.
To successfully setup a linked table I need to manually make sure that the table or view with the correct name and the correct layout is present in every tenant database. That is a job for the SQL Server Management Studio (SSMS) and can’t be done within NAV. Doing this also makes upgrades more difficult and can get in the way of a normal synchronization of metadata and tables.
Moving up to NAV 2016 I wanted to get out of this model and use the External SQL methods now available.
With these properties we can select the table or view name as the ExternalName and the table or view schema as the ExternalSchema. For all fields in the table we can define an ExternalName. If that is not defined the normal NAV field name will be used.
This option basically opens the door from the NAV Server to any SQL table. So, how do we get this to work?
I will show you how I moved from the Linked Table method to the External SQL method. If you take another look at the properties available for an External SQL table you will see that the DataPerCompany property is not available. So, an External SQL table is just a table definition for NAV to use and with C/AL code you can define where to find the external table. This gives you the flexibility to have the same table with all companies and all tenants or select different by tenants and/or companies.
In Iceland we have a national registry. That registry holds the registration details for every person and every company. Some companies buy access to the data from the national registry and keep a local copy and are allowed to do a lookup from this data. Since the data in this table is updated centrally but every company in every tenant wants to have access this is a good candidate for the External SQL table method.
I already had the table defined in NAV with needed data. I was able to find that table with SSMS.
By using this table I did not have to worry about the ExternalName for each column in my table definition since it already matched the NAV field names.
I found my application database and used the script engine in SSMS to script the database creation. I updated the database name to create a central database for my centralized data. I choose to use this method to make sure that the new database has the same collation as the NAV application database.
I scripted the National Register table creation and created the table in my centralized database. Then combined the scripts from INSERT INTO and SELECT FROM to insert data into my centralized table.
Finally I made sure that the service user running the NAV service had access to my centralized database. By doing this I can use a trusted connection between the NAV server and the SQL server.
Moving to NAV developement environment and into the table properties.
The ExternalName and ExternalSchema must match the table I created. Look at the picture from the SSMS to see “FROM [dbo].[National Register]”. There you can pick up what you need to specify in these properties.
When these changes are synchronized to my database NAV will remove the previous National Register table from the NAV database. That requires a synchronization with force so be careful.
The actual connection to the centralized database must be done in C/AL. More information is on this MSDN article.
To complete this solution I use several patterns.
I need a setup data to point me to the centralized database. I like to look at this as an external service so I link the setup to the Service Connections, Waldo calls this the Discovery Event Pattern. I create the following function in a Codeunit to register the service.
So, if the user has write access to the National Register Setup he will see this service listed in the service connections.
The link to an external database might require me to save a user name and a password. To successfully do this I apply another pattern for password encryption. I normally point people to the OCR service setup table and page to find out how to implement these password patterns.
I like to have the Enabled field on my setup tables. When the service is enabled the user can’t modify the setup data and when trying to enable the service the setup data is verified. Fields on the setup page are protected by using the EditableByNotEnabled variable.
I don’t think you will find a pattern for this method but the setup table in other details follows the Singelton pattern.
When the user tries to enable the service I do a series or error testing. The error testing are done with the combination of the Error Message pattern and the TryFunction pattern.
Line 21 does the actual connection test with a TryFunction.
Now, how to connect to the centralized data?
In my setup table I store the database server name and the database name within that server. With this data I create the connection string.
The table connection must have a unique id. I like to create a function to return variables that are linked to the functionality – not using the text constants.
This combines what I need to do. With the correct connection string C/AL registers the connection to my centralized database and set that connection as a default connection for the user. When NAV needs to use the data from the National Register C/AL must register the connection.
Adding a call to this Codeunit from every page and every function that uses the National Register.
Now back to my TryFunction, I can simply check if I can do a FINDFIRST without an error.
2 Replies to “Sharing data with multiple tenants”
There is a connection pool in the NAV Service Tier – All usages should use one connection. You can then just register every connections doing CompanyOpen. Please see the CRM Integration in std. which has this implementation in Codeunit 40 LogInManagement.
Note this is only related to the connection part – then you do not have to call this Codeunit from every page before run.
Excellent article! Tried to test most of this, but I must be something wrong.
While I implemented a similar full CheckOrRegisterExternalSQLConnection code in a codeunit, and it seems to work (at least it passes without errors), I still can’t figure out how does registering a table connection name with REGISTERTABLECONNECTION and setting SETDEFAULTTABLECONNECTION relates with the attempt trying the FindFirst in a GEVTest table of TableType=ExternalSQL, ExternalName=Test and ExternalSchema=dbo.
I mean, how does NAV knows that to access GEVTest, it has to use the connection name provided earlier? Is it so obvious it should use the one set from the latest SETDEFAULTTABLECONNECTION?
These doubts exist, because the issue I’m finding is that after running the CheckOrRegisterExternalSQLConnection if I do a tryfunction like the following, I immediately get “Cannot establish a connection to the SQL Server/Database.(…)” Break On Error. The TryFunction is:
IF NOT HASTABLECONNECTION(TABLECONNECTIONTYPE::ExternalSQL,Code) THEN
Found := GEVTest.FINDFIRST;
Could this be happening because I avoided doing the “Service Connection” registration part, since I don’t need the user to see it?
Another question is, how would you suggest to re-use an ExternalName property to open each time a different remote table, depending, e.g. on the Company name that NAV uses in SQL? Imagine this for TableTypeExternalSQL, table name MyCustomer, where you want to access C1$Customer, C2$Customer, C3$Customer, one company table at a time.