Entwicklung 29. September 2026 7 min Lesezeit

N:M-Beziehungen in Power Pages lesen: Web API vs. Liquid FetchXML

Die Portal Web API stolpert beim Lesen von N:M-Beziehungen. Warum das passiert, was Microsoft dazu dokumentiert, und wie Liquid FetchXML es serverseitig loest.

Der Use Case ist banal: Zu einer Installation die verknuepften Produkte anzeigen, verbunden ueber eine Many-to-Many-Beziehung. Man greift zur Portal Web API, ruft den Navigationspfad ab, und bekommt einen Fehler statt Daten.

Danach beginnt die uebliche Suche an den falschen Stellen: Site Settings, Berechtigungen, Schreibweise der Navigation Property. Die Wahrheit ist differenzierter als "die Web API kann kein N:M". Dieser Beitrag zeigt zwei Wege: was die Portal Web API wirklich kann (und wo sie stolpert) und wie Liquid FetchXML das N:M-Lesen serverseitig loest. Beides habe ich im Demo-Portal nachgestellt (Enhanced Data Model, zwei eigene Tabellen Installation und Produkt mit N:M-Beziehung). Einen dritten Weg – Server Logic (on-demand, serverseitiges JavaScript) – behandle ich in einem eigenen Folge-Post.

Weg 1: Portal Web API – was sie kann und wo sie stolpert

Die Portal Web API (/_api/...) bietet laut Microsoft ein Subset der Dataverse-Operationen: Lesen, Anlegen, Aktualisieren/Loeschen und Associate/Disassociate. $expand ist dokumentiert fuer single-valued (N:1) und collection-valued (1:N) Navigation Properties, letzteres aber nur eine Ebene tief. Ein N:M-$expand-Beispiel fehlt in der Portal-Doku komplett.

Der belastbare, dokumentierte N:M-Haken ist ein Known Issue. Microsoft schreibt woertlich:

"Users get a CDS error if they invoke a GET Web API request for tables that have multiple levels of one-to-many or many-to-many table permissions when Parental, Contact, or Account scopes add more conditions to the query. To resolve this issue, use FetchXML in the OData query."

Genau dieses "use FetchXML" ist der offizielle Ausweg – dazu gleich. Was ueber die Portal Web API dagegen sauber funktioniert, ist das Verknuepfen und Loesen von N:M-Beziehungen ueber $ref (im Demo bestaetigt):

webapi.safeAjax({
  type: "POST",
  url: "/_api/pwrprtl_installations(" + installationId + ")/pwrprtl_installation_product/$ref",
  contentType: "application/json",
  data: JSON.stringify({
    "@odata.id": baseUrl + "/_api/pwrprtl_products(" + productId + ")"
  })
});

Merksatz: N:M schreiben (associate) ueber die Web API ist unproblematisch. Das komfortable N:M-Lesen ueber $expand ist es nicht – dafuer gibt es den FetchXML-Weg.

Weg 2: Liquid FetchXML – Microsofts eigener Workaround

FetchXML loest N:M serverseitig ueber verschachtelte link-entity-Elemente, wobei die automatische Intersect-Tabelle mit intersect="true" als Join dient. Im Web Template:

{% fetchxml products %}
<fetch>
  <entity name="pwrprtl_product">
    <attribute name="pwrprtl_product_name" />
    <link-entity name="pwrprtl_installation_product" from="pwrprtl_productid" to="pwrprtl_productid" intersect="true">
      <link-entity name="pwrprtl_installation" from="pwrprtl_installationid" to="pwrprtl_installationid">
        <filter>
          <condition attribute="pwrprtl_installationid" operator="eq" value="{{ installationId }}" />
        </filter>
      </link-entity>
    </link-entity>
  </entity>
</fetch>
{% endfetchxml %}

<ul>
{% for p in products.results.entities %}
  <li>{{ p.pwrprtl_product_name }}</li>
{% endfor %}
</ul>

Im Demo-Portal liefert genau das die verknuepften Produkte einer Installation:

Die Liquid-FetchXML-Seite im Portal listet die ueber die N:M-Beziehung verknuepften Produkte einer Installation

Der Stolperstein, der mich beim Nachstellen Zeit gekostet hat (und der Sie sonst genauso trifft): Liquid FetchXML laeuft im Kontext des angemeldeten Nutzers und respektiert dessen Web-Rolle und Table Permissions. Als ich die Seite anonym aufrief, kam eine leere Liste zurueck – keine Fehlermeldung, nichts. Erst als Portal-Contact angemeldet (mit einer Web-Rolle, der Lese-Table-Permissions auf die beteiligten Tabellen zugewiesen sind) erschienen die Daten. Setzen Sie also Read-Table-Permissions auf die beteiligten Tabellen fuer die passende Web-Rolle; ich habe zusaetzlich eine Permission auf die Intersect-Tabelle gesetzt und nicht isoliert, ob sie zwingend noetig ist – im Zweifel mitnehmen.

Charakteristik: Die Daten stehen beim Seiten-Rendering bereit (vorgeladen). Ideal, wenn die Liste ohnehin gleich sichtbar ist.

Entscheidungshilfe

Aufgabe Empfohlener Weg
N:M lesen, Liste beim Page-Load sichtbar Liquid FetchXML (link-entity/intersect)
N:M schreiben (verknuepfen/loesen) Portal Web API $ref (POST/DELETE)
1:N / N:1 lesen Portal Web API $expand (eine Ebene) oder Liquid

Die eigentliche Lehre steckt weniger im "welcher Weg geht" als im Kontext: Beide Wege respektieren Web-Rolle und Table Permissions des Nutzers. Ein leeres Ergebnis ist deshalb oft kein Bug im Code, sondern eine fehlende Berechtigung oder ein nicht angemeldeter Nutzer. Bevor Sie die FetchXML zerlegen, pruefen Sie erst, als wer die Seite laeuft.

Ausblick: Es gibt einen dritten Weg, N:M-Daten zu holen – Server Logic, serverseitiges JavaScript, das der Client on-demand aufruft (seit 2026 allgemein verfuegbar). Wann sich das gegenueber dem vorgeladenen FetchXML lohnt und worauf man dabei achten muss, kommt in einem eigenen Folge-Post.

Weiterfuehrende Artikel

Quellen

Tino Rabe

Tino Rabe

Power Pages Spezialist · Former Microsoft MVP

Power Pages Spezialist, former Microsoft MVP. Ich unterstütze Unternehmen bei sicheren Kundenportalen: vom Architektur-Workshop über Coaching bis zum Security-Audit.

Wann wurde Ihr Portal zuletzt unabhängig geprüft?

Security-Audit zum Festpreis, oder einfach erstmal sprechen.

Termin buchen