Arbeiten mit Dataverse-Daten mithilfe des Python SDK

In diesem Artikel wird Beispielcode veranschaulicht, mit dem das SDK zum Arbeiten mit Dataverse-Daten und -Metadaten verwendet wird. Bevor Sie fortfahren, lesen Sie unbedingt "Erste Schritte".

Grundlegende Vorgänge

Hier ist Beispielcode, der auf der Kontotabelle ausgeführt wird.

from azure.identity import InteractiveBrowserCredential
from PowerPlatform.Dataverse.client import DataverseClient

# Replace <myorg> with the name of a valid environment.
base_url = "https://<myorg>.crm.dynamics.com"
client = DataverseClient(base_url=base_url, credential=InteractiveBrowserCredential())

# Create a record
account_id = client.records.create("account", {"name": "Contoso Ltd"})

# Read a record
account = client.records.retrieve("account", account_id)
print(account["name"])

# Read with expand fetches a related record in the same HTTP request
account = client.records.retrieve(
    "account", account_id,
    select=["name"],
    expand=["primarycontactid"],
)
contact = (account.get("primarycontactid") or {})
print(contact.get("fullname"))

# Update a record
client.records.update("account", account_id, {"telephone1": "555-0199"})

# Delete a record
client.records.delete("account", account_id)

Kontext-Manager

Der Kontext-Manager verarbeitet automatische Bereinigung und HTTP-Verbindungspooling. Verwenden Sie die folgende Syntax, um den Kontext-Manager zu nutzen.

with DataverseClient("https://<myorg>.crm.dynamics.com", credential) as client:

Der folgende Arbeitscode veranschaulicht die Verwendung des Kontext-Managers.

from azure.identity import InteractiveBrowserCredential
from PowerPlatform.Dataverse.client import DataverseClient

# Connect to Dataverse
credential = InteractiveBrowserCredential()

with DataverseClient("https://<myorg>.crm.dynamics.com", credential) as client:

    # Create a contact
    contact_id = client.records.create("contact", {"firstname": "John", "lastname": "Doe"})

    # Read the contact back
    contact = client.records.retrieve("contact", contact_id, select=["firstname", "lastname"])
    print(f"Created: {contact['firstname']} {contact['lastname']}")

    # Clean up
    client.records.delete("contact", contact_id)

# Session closed, caches cleared automatically

Massenvorgänge

Im Folgenden sind einige Beispiele aufgeführt, die Massenvorgänge ausführen.

# Bulk create
payloads = [
    {"name": "Company A"},
    {"name": "Company B"},
    {"name": "Company C"}
]
ids = client.records.create("account", payloads)

# Bulk update (broadcast same change to all)
client.records.update("account", ids, {"industry": "Technology"})

# Bulk delete
client.records.delete("account", ids, use_bulk_delete=True)

Im folgenden Beispiel werden mehrere Konten erstellt. Übergeben Sie eine Nutzlastliste an create(logical_name, payloads), um die sammlungsgebundene Aktion Microsoft.Dynamics.CRM.CreateMultiple aufzurufen. Die Methode gibt erstellte Datensatz-IDs zurück list[str] .

# Bulk create accounts (returns list of GUIDs)
payloads = [
    {"name": "Contoso"},
    {"name": "Fabrikam"},
    {"name": "Northwind"},
]
ids = client.records.create("account", payloads)
assert isinstance(ids, list) and all(isinstance(x, str) for x in ids)
print({"created_ids": ids})

Weitere Informationen zu Massenvorgängen:

  • Gibt None (identisch mit einzelnem Update) zurück, um die Semantik konsistent zu halten.
  • Ob es sich um Broadcast oder pro Datensatz handelt, hängt davon ab, ob der Parameter changes ein Wörterbuch oder eine Liste ist.
  • Das Primärschlüsselattribut wird beim Erstellen von UpdateMultiple-Aktionszielen automatisch eingefügt.
  • Wenn eine Nutzlast @odata.type auslässt, fügt das SDK diese automatisch ein (Suche nach logischen Namen im Cache).
  • Die Antwort enthält nur IDs – das SDK gibt diese GUID-Zeichenfolgen zurück.
  • Die Erstellung eines einzelnen Datensatzes gibt eine Ein-Element-Liste von GUIDs zurück.
  • Die Metadatensuche für @odata.type wird einmal für jeden Entitätssatz (im Arbeitsspeicher zwischengespeichert) durchgeführt.

Upsert (Erstellen und Aktualisieren)

Eine allgemeine Datenzugriffssequenz besteht darin, zuerst zu überprüfen, ob eine Tabellenzeile vorhanden ist. Wenn die Zeile vorhanden ist, aktualisieren Sie sie. Andernfalls erstellen Sie die Zeile. Sie können diese Sequenz effizienter gestalten, indem Sie einen einzelnen API-Aufruf des Upsert-Vorgangs verwenden.

Weitere Informationen finden Sie unter Verwenden von Upsert zum Erstellen oder Aktualisieren eines Datensatzes.

Important

Für die in alternate_key verwendeten Spalten muss in Dataverse ein alternativer Schlüssel für die Tabelle konfiguriert sein. Definieren Sie alternative Schlüssel in den Metadaten der Tabelle über das Power Apps Maker-Portal oder einen Dataverse-API-Aufruf. Ohne einen konfigurierten alternativen Schlüssel lehnt Dataverse Upsert-Anforderungen mit einem Fehler von 400 ab.

Verwenden Sie client.records.upsert(), um Datensätze zu erstellen oder zu aktualisieren, die durch alternative Schlüssel identifiziert werden. Wenn der Schlüssel einem vorhandenen Datensatz entspricht, aktualisiert die Methode den Datensatz. Andernfalls wird der Datensatz erstellt. Ein einzelnes Element verwendet eine PATCH-Anfrage, während mehrere Elemente die UpsertMultiple Sammelaktion verwenden.

from PowerPlatform.Dataverse.models.upsert import UpsertItem

# Upsert a single record
client.records.upsert("account", [
    UpsertItem(
        alternate_key={"accountnumber": "ACC-001"},
        record={"name": "Contoso Ltd", "telephone1": "555-0100"},
    )
])

# Upsert multiple records (uses UpsertMultiple bulk action)
client.records.upsert("account", [
    UpsertItem(
        alternate_key={"accountnumber": "ACC-001"},
        record={"name": "Contoso Ltd"},
    ),
    UpsertItem(
        alternate_key={"accountnumber": "ACC-002"},
        record={"name": "Fabrikam Inc"},
    ),
])

# Composite alternate key (multiple columns identify the record)
client.records.upsert("account", [
    UpsertItem(
        alternate_key={"accountnumber": "ACC-001", "address1_postalcode": "98052"},
        record={"name": "Contoso Ltd"},
    )
])

# Plain dict syntax (no import needed)
client.records.upsert("account", [
    {
        "alternate_key": {"accountnumber": "ACC-001"},
        "record": {"name": "Contoso Ltd"},
    }
])

Datenrahmen

Das SDK stellt Pandas Wrapper für alle CRUD-Vorgänge über den client.dataframe Namespace bereit. Diese Wrapper verwenden die DataFrame- und Series-APIs von Pandas für Eingabe und Ausgabe.

Note

client.dataframe.get() ist veraltet. Verwenden Sie die ga-Muster, die in den folgenden Abschnitten gezeigt werden.

import pandas as pd
from PowerPlatform.Dataverse.models.filters import col

# Query records as a single DataFrame (GA builder pattern)
df = (client.query.builder("account")
      .select("name", "telephone1")
      .where(col("statecode") == 0)
      .execute()
      .to_dataframe())
print(f"Found {len(df)} accounts")

# Limit results with top for large tables
df = client.query.builder("account").select("name").top(100).execute().to_dataframe()

# Create records from a DataFrame (returns a Series of GUIDs)
new_accounts = pd.DataFrame([
    {"name": "Contoso", "telephone1": "555-0100"},
    {"name": "Fabrikam", "telephone1": "555-0200"},
])
new_accounts["accountid"] = client.dataframe.create("account", new_accounts)

# Update records from a DataFrame (id_column identifies the GUID column)
new_accounts["telephone1"] = ["555-0199", "555-0299"]
client.dataframe.update("account", new_accounts, id_column="accountid")

# Clear a field by setting clear_nulls=True (by default, NaN/None fields are skipped)
df = pd.DataFrame([{"accountid": new_accounts["accountid"].iloc[0], "websiteurl": None}])
client.dataframe.update("account", df, id_column="accountid", clear_nulls=True)

# Delete records by passing a Series of GUIDs
client.dataframe.delete("account", new_accounts["accountid"])

# SQL query directly to DataFrame (supports JOINs, aggregates, GROUP BY)
df = client.dataframe.sql(
    "SELECT a.name, COUNT(c.contactid) as contacts "
    "FROM account a "
    "JOIN contact c ON a.accountid = c.parentcustomerid "
    "GROUP BY a.name"
)

Hochladen von Dateien in Dataverse

Das folgende Beispiel zeigt, wie Sie eine Datei mit dem Namen document.pdf in die Spalte „Datei“ mit dem Namen new_Document eines Kontodatensatzes hochladen. Das SDK für Python behandelt automatisch Dateiabschnitte für Dateien, die größer als 128 MB sind.

# Upload a file to a record
client.files.upload(
    "account",
    account_id,
    "new_Document",
    "/path/to/document.pdf",
)

Tip

Wenn die Dateispalte nicht vorhanden ist, erstellt das SDK sie automatisch.

Batchvorgänge

Verwenden Sie client.batch, um mehrere Vorgänge in einer einzigen HTTP-Anfrage zu senden. Der Batch-Namespace entspricht client.records, client.tables und client.query.

# Build a batch request and add operations
batch = client.batch.new()
batch.records.create("account", {"name": "Contoso"})
batch.records.create("account", [{"name": "Fabrikam"}, {"name": "Woodgrove"}])
batch.records.update("account", account_id, {"telephone1": "555-0100"})
batch.records.delete("account", old_id)
batch.records.retrieve("account", account_id, select=["name"], expand=["primarycontactid"])  # single record with expand
batch.records.list(                                                # multi-record, single page
    "account",
    filter="statecode eq 0",
    select=["name"],
    orderby=["name asc"],
    top=50,
)

result = batch.execute()
for item in result.responses:
    if item.is_success:
        print(f"[OK] {item.status_code} entity_id={item.entity_id}")
    else:
        print(f"[ERR] {item.status_code}: {item.error_message}")

Transaktionsänderungenet

Alle Vorgänge in einem Changeset werden entweder erfolgreich ausgeführt oder gemeinsam zurückgesetzt.

batch = client.batch.new()
with batch.changeset() as cs:
    lead_ref = cs.records.create("lead", {"firstname": "Ada"})
    contact_ref = cs.records.create("contact", {"firstname": "Ada"})
    cs.records.create("account", {
        "name": "Babbage & Co.",
        "originatingleadid@odata.bind": lead_ref,
        "primarycontactid@odata.bind": contact_ref,
    })
result = batch.execute()
print(f"Created {len(result.entity_ids)} records atomically")

Tabellenmetadaten und SQL-Abfragen in einem Batch

batch = client.batch.new()
batch.tables.create("new_Product", {"new_Price": "decimal", "new_InStock": "bool"})
batch.tables.add_columns("new_Product", {"new_Rating": "int"})
batch.tables.get("new_Product")
batch.query.sql("SELECT TOP 5 name FROM account")

result = batch.execute()

Bei Fehler fortfahren

Versuchen Sie alle Vorgänge auch dann, wenn ein Fehler auftritt.

result = batch.execute(continue_on_error=True)
print(f"Succeeded: {len(result.succeeded)}, Failed: {len(result.failed)}")
for item in result.failed:
    print(f"[ERR] {item.status_code}: {item.error_message}")

DataFrame-Integration

Führen Sie pandas DataFrames direkt einem Batch zu.

import pandas as pd

batch = client.batch.new()

# Create records from a DataFrame
df = pd.DataFrame([{"name": "Contoso"}, {"name": "Fabrikam"}])
batch.dataframe.create("account", df)

# Update records from a DataFrame
updates = pd.DataFrame([
    {"accountid": id1, "telephone1": "555-0100"},
    {"accountid": id2, "telephone1": "555-0200"},
])
batch.dataframe.update("account", updates, id_column="accountid")

# Delete records from a Series
batch.dataframe.delete("account", pd.Series([id1, id2]))

result = batch.execute()

Ein vollständiges Batchbeispiel finden Sie unter examples/advanced/batch.py.

Siehe auch