Understanding why circular dependencies are problematic but easy to create and how to avoid them.
Whenever you create a solution-aware component in Power Platform (Dataverse), there are often other components that that component needs to function. These are the dependencies of the component.
For example:
if you create a view, it will depend on each of the columns that you’ve used to display or for the filters. Those columns must be there for the view to work.
if you create a relationship it will depend on the target table that it points at. That table must be there for the relationship to work.
You can see the dependencies for any component in the Power Apps/Automate maker portal, by selecting the “Show dependencies” action:
The “Uses” tab shows the outgoing dependencies on which the current component depends.
The “Used by” tab shows the incoming dependencies - the components that depend on the current component.
The dependencies are displayed grouped by the solution that provides (the base layer of) the component. This will always be “Unmanaged solution” for unmanaged components, but for managed components, this will display a specific solution name.
When you export a solution, Dataverse creates a list of all the components on which components in the current solution depend but which aren’t also present in that same solution. These are stored in the MissingDependencies
element in the solution.xml
manifest inside the solution.
When you try to import a solution this list is used as a quick way to check that all of the required components are present. If any of these are missing, the import will fail.
We can summarise the other solutions a solution depends on which others by summarising the solutions that appear in this list.
So if the current solution “Solution A” depends on 3 components in “Solution B” and 4 in “Solution C”, we can summarise that Soluton A depends on B and C.
Note: Strictly speaking, the actual solution which provides each component is not significant as the platform does not enforce this. We could provide Table 4 via a different solution and as long as the component is present at the time of check, the platform does not care that it didn’t come from “Solution C” as expected. The solution names are shows simply as a convininece to help us.
Circular solution dependencies are when we create a set of solutions that all circularly depend on each other, directly or indirectly based on the components in each solution.
Here’s the most simple case of circular dependencies between the components in 2 solutions:
Each has a component that directly depends on the other. So the dependencies are circular; A depends on B which depends on A.
But circular dependencies don’t have to be this simple. We might have more than 2 solutions involved, or other unrelated components in the same solutions that are causing the dependencies. It’s about the overall dependencies for the solution rather than for individual components. In this example, there is a circular dependency between all of these solutions, but caused by different components:
Overall in this example, there is a circular dependency between the solutions A=>B=>C and then back to A. It’s caused by how we’ve organised the solutions - we could move things around to avoid this. Note that on a component level in this example, there is no circular dependency.
As you can see from the examples above, it’s trivial to create circular dependences and they can’t be created in so many different ways. The platform doesn’t do anything to help us identify and/or avoid them.
Circular dependencies create a chicken and egg problem; Which can be installed first?
None of them!
The problem is, we can’t install any of them first (into a clean environment - see below) because they depend on the others:
Solution A needs B so we can’t install it.
Solution B needs A so we can’t install it
We are stuck!
The chicken and egg problem doesn’t stop there.
Not only does it make it impossible to install the solutions. It also makes it impossible to remove the solutions. If solution A depends on B and vice-versa, we cannot remove either of them directly.
This also plays out sometimes when you are trying to evolve your solutions by removing components or moving them around. The circular dependencies at a component level being split across multiple solutions can prevent the solution upgrade from succeeding.
There is sometimes a caveat that can hide the circular dependencies problem which is often the reason many teams don’t realise they have created this problem until well into the future:
Let’s say you have v1 of your solutions that already contain tables A and B, but no circular relationship between them (v1):
You deploy this to your test environment. No problem.
Now you create the relationship between B and A (v2):
You now deploy to the environment that already has v1.
Because B and A are already present in this environment, deployment will succeed. (In this case, it will work in either order).
The problem with this is that since deploying to environments that don’t already have the preceding version comes much less frequently, you might not hit these issues until it’s too late. That could be when you’re trying to deploy to your production environment (which is updated less frequently and never got v1 deployed at all), or much later when you are trying to refactor/remove your solutions.
Nobody wants to spend an extra 4 hours working out how to untangle such a problem during the production go-live! Never mind the risks involved if they try to solve it ad-hoc and invalidate all the testing that has been done.
So… summary… Whilst you might be able to get away with this a lot of the time… it’s best to avoid creating solutions with circular dependencies at all if you want to avoid this pain!
Once you’ve created a circular dependency situation in an environment, it can be complex to work out how to recover from it.
Microsoft has documented one example here:
So recovering from these situations is a multi-step process. If you’re having to do this as part of your prod deployment etc (because you only just found out you have this issue), it’s a very dangerous thing to be doing without testing.
The number one way to avoid dependency issues, in general, is to simply avoid breaking your solution up into multiple solutions and instead use a single solution, or at least fewer solutions.
Here’s the scenario above, but using a single solution:
Now there’s no problem. With everything inside a single solution, all the components that depend on each other are inside that single solution and there are no external solution dependencies to worry about.
If we did want to still segment into multiple solutions, it could look like this:
Now again, we have no problem, because moving some of the tables has removed the inter-solution dependencies.
The solutions at the top of the list must never depend on solutions lower down the list.
e.g.
Solution A
Solution B
Solution C
So:
Solution A must have no dependency on B or C
Solution B can depend on A but not C
Solution C can depend on A and/or B
It’s important to realise that it’s very hard to respect these rules if you try to follow hard and fast rules like “all tables in solution A, all Flows in solution B”.
Instead, you must segment based on the actual dependencies between the components. These change rapidly as your development moves on and also as the platform continues to develop, introducing new combinations of dependencies that can occur between component types. It is often easier to segregate only functional/module boundaries instead.
This ensures that it is impossible to create dependencies in the wrong direction. The downside is that this involves a lot of ongoing effort.
If you choose not to do this:
or