Row-level security is easy to demo and hard to live with. The version most teams ship hard-codes who sees what, and the first reorg, a new region, a merged team, a transferred patch, quietly breaks it. The fix is to stop encoding the structure in roles and start driving it from data.
Static roles are a trap
Static RLS defines a role per group and a filter per role. It works on day one. The problem is that every change to the organisation means editing roles in the model and republishing. In housing, where patches, contractors and teams reshuffle constantly, that is a standing maintenance cost you signed up for without noticing.
Under the hood, RLS evaluates a DAX expression that returns true or false for each row, and only the rows that return true stay visible. Static roles bake the membership into that expression. Dynamic roles look it up instead.
Drive it from a mapping table
Dynamic RLS uses the signed-in user’s identity to find them in a security table, then filters the data by whatever that table says they can see. The role never changes. When someone moves team, you update one row in the mapping table, not the model.
[Patch] IN
SELECTCOLUMNS(
FILTER(
UserPatch,
UserPatch[Email] = USERPRINCIPALNAME()
),
"Patch", UserPatch[Patch]
)Now a reorg is a data task. The mapping table can be maintained outside the model, in the warehouse or a simple managed list, so the people who own access do not need to open Power BI Desktop at all.
Test it the way it will fail
Use View as role on the Modeling ribbon to check filters before publishing, and measure the cost with Performance Analyzer, because RLS filters add query work and a heavy mapping relationship can slow every visual. Map security groups to roles rather than individuals wherever you can, so membership is managed once, centrally.
RLS should be invisible. Build it so the next reorg is a row in a table, and it will be.