Virtual Tables – Custom Ownership

This will be a follow-up post on my session about Virtual Tables at Nordic Summit. People have asked me if there is a blog post about how to set up custom security on SQL Virtual Tables that are available out of the box via the virtual connector.

The Problem

Virtual Tables are organization owned and that makes them quite limited in real business world scenarios where some users should not see all the data that is stored in the database. We will show how to implement a basic security model around them to make them more usable.

This example will use Azure SQL Virtual connector, but it’s working just the same with any other data source that is available out there. We need a custom owner field since there is no out-of-the-box one on the virtual table defined.

Polymorphic Lookup Issue

The owner on a normal table can be a team or a user, but on a virtual table, it’s kinda tricky. The problem lays in polymorphic lookup for virtual tables. You can create a polymorphic lookup, with a help of some code because it’s not available via UI, but it’s acting a bit weird when you see it for the first time.

The polymorphic lookup field will store only GUID in your external data source and because of that, you will have a problem while rendering the value in the UI. Lookup picker will allow you to choose tables defined in the lookup just fine, but the problem starts when you try to present the value to the user. It will be rendered in the right way for just the primary relationship defined in the lookup field and all other values will be rendered as No Name and will point to the wrong table.

Since we now know the limitations we will try to implement the security with a simple lookup this time until there is full support for polymorphic lookups in virtual tables space.

Prerequisites

We will not cover the whole process of creating a SQL virtual table via a virtual connector in this blog so if you are interested in how to do that there is a pretty well-written step-by-step guide by Microsoft here.

We will start building the extension to the virtual table that already exists in the Dataverse.

Solution Overview

We will create a simple virtual table against the SQL database which will contain two owner columns: one as a lookup to the team and the second one as a text column. Then we will create two plugins to help us achieve the final goal.

Steps:

  • Create duplicate owner ID fields in the SQL table
  • Create a custom lookup field that points to the team table
  • Create a single line of text field to store GUID as text
  • Implement a plugin that will store GUID as text on owner change
  • Implement a plugin that will inject filters on RetrieveMultiple message

SQL Table

Let’s create a simple SQL table as shown below.

There are only two columns that you need to include here. Those are OwnerId and OwnerIdString columns.

OwnerId will be used for a lookup field and OwnerIdString will store the text representation of the GUID selected in the lookup.

You may ask why do we need 2 fields when we can use only one lookup?

The answer is that currently there is a limitation on a virtual table where you can’t filter it by the lookup field so we need to filter it by the text instead, but we still want users to be able to edit the ownership in the lookup field like they are used to.

Ok, but still do we really need two fields in the SQL table or we can use just one?

There is also another limitation of the virtual table that doesn’t allow us to use the same column twice in the virtual table as an external name so we need to duplicate data in our data source.

SQL table should look something like this one:

That’s all we need to do in SQL to make it work, let’s jump into the Dataverse.

Lookup Field

First, we need to make a lookup field that will point to the Team table. This field will be exposed to the users via UI and users will be able to update it just like any other field.

There is nothing special about this one, just a simple lookup field that must have an external name set to the OwnerId column in the SQL table.

Text Field

The second field that we need is a single line of text field that will store the string representation of the GUID.

We should map this one, by setting the external name, to the second Owner field in the SQL database, OwnerIdString in this case.

Update plugin

Users should not experience our little hack with two fields that will hold the same value so we need to automate the process of copying the data to the second field first.

Let’s create a simple plugin that will be triggered when the Owner lookup field is changed.

There is really nothing spectacular happening in this plugin. It will only copy the GUID to the string field every time the owner field is changed.

We should trigger it as a pre-operation plugin step on the change of an owner field.

Now we can jump to the most important part.

RetriveMultiple Plugin

RetrieveMutliple plugins don’t get so much love from people in general and I think that they definitely deserve to be used more since there is so much potential there.

What will we do here?

We will intercept the RetrieveMultiple message and add some prefiltering to the original query so we filter out records that should not be shown to the user.

First of all, we need to normalize the query we get in the plugin since you will not always get QueryExpression which is easy for manipulation. Sometimes you will get FetchExpression instead and we need to convert it to QueryExpression.

It can be done with a simple helper function:

Now we have a QueryExpression every time, perfect!

Since we will do the filtering based on the owner team we need to get all the teams that our user is a member of.

When we have teams we can start editing the initial query.

Since we need to add our filters on top of all other conditions in the query we need to create a FilterExpression first to hold our conditions and set the operator to OR.

Then we should loop through all the teams we got in the GetUserTeams function and add them as ConditionExpressions to the filter.

In the end, we should append our filter to the original query and set the Query input parameter to the newly updated query.

Here is the whole plugin code:

When we have all code written it’s time to register a new RetrieveMultiple plugin step in the Plugin Registration Tool.

Make sure that you register it as a sync Pre-operation plugin, otherwise you will not be able to edit the Query parameter.

Final Solution

Let’s see how it works. I have created records that have different owning teams, but my user is a member of only one of them.

Here is the list of all records before we turn the RetrieveMultiple plugin.

After we turn on the plugin we should see the filtered list. The user in this example is a member of Team Rocket so all the records that are owned by Team Pikachu will not be shown to him in the list.

This is the situation after we turned on the plugin:

Finally, we can have a working example for basic security around the virtual tables. Woohoo!

Conclusion

Working with virtual tables is still not perfect and we still need to pull out some hacks, but the situation is getting better with each release.

This method is not tied to SQL data sources so feel free to try it with any other data source out there.

I hope that in the future we will be able to add working polymorphic lookups to the virtual tables which will help us to achieve the final goal and that is the owner field that we are used to.

Until then we can go this basic route that allows us to edit the owner or simply hide the ownership from our users in the UI and set it in the hidden text field so we can do the filtering that way.

Some emails are not synced to D365

sync-fail.png

Dynamics 365 and Exchange integration is plug & play with some minor configuration…I just don’t think so.

Lately, I had an issue that emails were not syncing to the Dynamics 365 using Exchange server via server-side synchronization. We spent like a month of intense monitoring and changing every single parameter in hope that emails will finally get synced without any issue.

Here are some of the tips that we used on our journey which finally made our integration work flawlessly.


Update to the latest Dynamics 365 version

The most basic thing to do is to update to the latest release of Dynamics 365, but of course, I know that it’s not always the best option and most people out there are considering it as the last option if everything else fails.

Image 233

Just trust me that more than few times I regret that I didn’t do an update first before I used too many hours of researching the issue.

Microsoft is adding tons of updated/hotfixes with every release that are not mentioned in the release notes. Last few versions had quite some updates related to the server-side sync mentioned in the notes.

The same email address on multiple entities

You should not use one email address on multiple OOB entities. It doesn’t matter if you are using it on 2 same records like contact entities for example or you just use it on 2 different records like on one user entity and one contact entity.

Image 235

You need to ensure that you just have one record on the instance that has a unique email address that is populated in the OOB email field (eg. emailaddress1,emailaddress2,..).

Syncing same email address on 2 instances

You can forget to disable email sync on the development instance when you finally move your email integration to the production instance.

The first step is to remove server profile from the mailbox on the development instance. Go to the mailbox that you want to sync on production, clear the server profile field and hit save button.

Image 240.png

The next step you need to do it test & enable the same mailbox on the production instance, but you need to be sure that you select the Sync items with Exchange from this CRM org only  checkbox on this step.

Image 239

After those steps, you are sure that emails will be synced only to the production instance.

Change mailbox ownership

The most strange tip is to change ownership of the mailbox form the TEAM to the USER.

Image 241

I know that this sounds ridiculous, but it actually helps in some situations. We came to this solution after talking to MS support that after a while suggested doing so and after that everything just started working like a charm.


I just hope that some of this tips will save you some time while investigating the syncing issue like this one.