Power Fx explained: Record scope, scope ambiguity and disambiguation

When answering questions on the Power Apps Community forum related to unexpected patching behavior containing a nested LookUp, column / table names not being recognized or a nested filter function suddenly returning all records, scope ambiguity is quite frequently the root cause. Because of this, I want to provide a comprehensive explanation and guide on record scope and disambiguation.

In this blog post we’ll (1) explore what record scope means, (2) when scope ambiguity could occur, (3) the possible issues caused by scope ambiguity and (4) how to solve this via a named record scope or the disambiguation operator. A mouthful of fancy words that may look scary at first, but after this blog post you’ll fully understand the core concepts and be able to add them to your daily vocabulary. 😉

Table of Contents

Setting the scene

Within this blog we’ll explore the inner workings of record scope and ambiguity. To further explain the concepts I will provide code snippets which make use of the colCatBreeds and colCats collections. The ClearCollect functions can be found & copied below.

				
					ClearCollect(
    colCats,
    {Name:"Furby", Age:2, Breed:"British Shorthair"},
    {Name:"Lucifer", Age:2, Breed:"Maine Coon"},
    {Name:"Hobbit", Age:5, Breed:"Selkirk Rex"}
);
ClearCollect(
    colCatBreeds,
    {BreedName: "British Shorthair"},
    {BreedName: "Maine Coon"},
    {BreedName: "Selkirk Rex"}
)
				
			

What is record scope?

I have to start off by saying that record scope is very well explained within the Microsoft documentation under the ‘Understand tables and records’ chapter. In this section however, I’ll provide a light & summarized version as an introduction but I would highly suggest giving the documentation a read as well.

Certain functions allow you to evaluate a formula or condition across all records of a table individually. Functions that instantly come to mind are functions that ‘loop’ through your data (e.g. ForAll, AddColumns, Concat, Distinct), aggregate data (Max, Min, Sum…) or query your datasource (LookUp, Filter) – a full list can be found in the documentation

In essence, you have the ability to access record columns within these functions. Without knowing the specific terminology, you may have already used record scope quite frequently within your Power Fx formulas. Let’s take the Filter function below as an example to make the explanation less abstract:

				
					Filter(
    colCats,
    Breed = "British Shorthair"
)

				
			

Since filter creates a record scope, we are able to access the record column ‘Breed’ within our function. In this case, the implicit scope reference ‘Breed’ can also be written out in full as ‘ThisRecord.Breed’. 

A similar, more visual example I frequently give as a comparison is the Gallery control. Within this control you have the ability to access record scope by using the ‘ThisItem’ operator. In many ways this is similar to our ‘ThisRecord’ operator within record scope functions.

Nested record scope

Our previous filter statement showcased an example in which only one record scope exists. When referencing the breed column, we can very easily trace this column back to the colCats collection. In certain cases however, we may want to nest record scope functions.

Let’s illustrate this with an example: We want to add a column to colCatBreeds containing all cats of colCats that match this specific breed. 

				
					AddColumns(
    colCatBreeds,
    'Cats',
    Filter(
        colCats,
        BreedName = Breed
    )
)

				
			

Although multiple scopes exist (colCatBreeds & colCats) we can still leverage the implicit scope reference to achieve our goal. This is the case because we don’t have conflicting column names – both colCatBreeds and colCats have a unique breed column name. That said, using disambiguation even in this case would improve readability.

As previously stated, an implicit reference can also be written out in full as ThisRecord.ColumnName. One thing to keep in mind however is that the explicit ThisRecord reference always refers to the innermost scope. ‘ThisRecord.BreedName = ThisRecord.Breed’ would result in an error stating that BreedName is not recognized as a valid column name because it refers to the outer scope.

Scope ambiguity

What does it mean?

Although our previous filter could be improved readability wise, the columns were disambiguous. In scenarios where multiple scopes and conflicting column names exist, however, scope ambiguity frequently arises.

We may want to compare similarly named columns across scopes, reference a variable that is similarly named to a column of our record scope and so on… I refer to ‘scope ambiguity’ when it is no longer apparent to which scope our reference points to and thus requires disambiguation.

How does it affect our code?

Let’s start from an example to further illustrate scope ambiguity in practice. Let’s take the same approach as our previous example, but slightly alter the colCatBreeds column name to conflict with our colCats Breed column. In other words, we’ll rename the BreedName column to Breed.

				
					ClearCollect(
    colCatBreeds,
    {Breed: "British Shorthair"},
    {Breed: "Maine Coon"},
    {Breed: "Selkirk Rex"}
)
				
			

 Initially we may have tried to simply use the same implicit record scope approach:

				
					AddColumns(
    colCatBreeds,
    'Cats',
    Filter(
        colCats,
        //Scope ambiguity
        Breed = Breed
    )
)

				
			

No errors means no problems, right? 😎 I’m sorry to disappoint you but that’s not the case. Since it’s not an actual technical or syntax error but rather a logical error, our code above will run without any issues. It is only after inspecting our collection that we notice something went wrong.

The Filter condition above is a textbook example of scope ambiguity. Due to the nested record scope (Filter & ForAll) and similar column names, it is no longer clear which scope we are referring to within our condition. In actuality, the inner scope will take priority when referencing  a column name that exists both in the inner and outer scope.

In other words, each colCatBreeds record will now contain all of our colCats records. This is caused due to our filter statement interpreting our condition as if we are comparing the same colCats Breed column twice – always returning true.

The most common scope related issues on the forum can be summarized in the following topics:

  • Unexpected behavior with nested scope functions
  • Outer scope columns not being available via the ThisRecord operator
  • Global objects (tables, variable names…) not being recognized within record scope functions 

How to avoid scope ambiguity

Creating a named record scope

My favorite approach to avoid scope ambiguity is by creating a named explicit record scope via the ‘As’ operator. This allows you to override the default ThisRecord operator / implicit scope. More importantly, defining a named scope allows you to reference the record in the outer scope. This approach is thus especially useful when nesting record scope functions. 

Let’s use this approach to fix our previous code example:

				
					AddColumns(
    //Explicit scope defined as Main
    colCatBreeds As Main, 
    'Cats',
    Filter(
        colCats,
        //No more scope ambiguity
        Main.Breed = Breed
    )
)

				
			

The ‘Main’ scope now allows us to easily distinguish both record scopes and access the outer scope within our filter condition. All columns prefixed with ‘Main.’ in our example will reference our collection record scope, whereas the implicit scope reference is linked to our colCats record.

Disambiguation operator

The previous example can also be resolved by using the disambiguation operator.

				
					//Column disambiguation structure
datasource[@columnName]
				
			

Using the disambiguation operator instead of a named scope would result in the following code:

				
					AddColumns(
    colCatBreeds,
    'Cats',
    Filter(
        colCats,
        //No more scope ambiguity
        colCatBreeds[@Breed] = Breed 
    )
)

				
			

In this case I generally prefer creating a named scope because of the added naming flexibility / readability benefits it provides. 

It is important to note however that the disambiguation operator can also be used to access global objects (variables, tables…)  within record scope. This comes in handy when a global object name may conflict with your current record’s column name.

To showcase this in action, let’s slightly adjust our previous use case. We now want to visualize all cats that match the age set in our ‘Age’ variable. Unfortunately, both our column and variable are named ‘Age’.

				
					//E.g. OnSelect of a button
Set(Age, 5)

				
			

As a first attempt we may have tried using the following Filter within our Gallery’s Items property:

				
					Filter(
    colCats,
    //Scope ambiguity
    Age = Age
)

				
			

Once again this results in scope ambiguity and unexpected behavior as all records are displayed in the gallery. Similar to our previous example, our condition compares the record’s age column twice. A record scope column name will take priority over the global object name it conflicts with.

To avoid ambiguity we can use the disambiguation operator to access our variable:

				
					Filter(
    colCats,
    //Variable = record scope column
    [@Age] = Age
)
				
			
Scroll to Top