Skip to content

Commit 480dc7a

Browse files
tpellissierclaude
andcommitted
Update SDK usage SKILL with namespaced API and relationship examples
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent fa3026c commit 480dc7a

File tree

2 files changed

+220
-78
lines changed
  • .claude/skills/dataverse-sdk-use
  • src/PowerPlatform/Dataverse/claude_skill/dataverse-sdk-use

2 files changed

+220
-78
lines changed

.claude/skills/dataverse-sdk-use/SKILL.md

Lines changed: 110 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: dataverse-sdk-use
3-
description: Guidance for using the PowerPlatform Dataverse Client Python SDK. Use when calling the SDK like creating CRUD operations, SQL queries, table metadata management, and upload files.
3+
description: Guidance for using the PowerPlatform Dataverse Client Python SDK. Use when calling the SDK like creating CRUD operations, SQL queries, table metadata management, relationships, and upload files.
44
---
55

66
# PowerPlatform Dataverse SDK Guide
@@ -17,6 +17,11 @@ Use the PowerPlatform Dataverse Client Python SDK to interact with Microsoft Dat
1717
- Custom columns: include customization prefix (e.g., `"new_Price"`, `"cr123_Status"`)
1818
- ALWAYS use **schema names** (logical names), NOT display names
1919

20+
### Operation Namespaces
21+
- `client.records` -- CRUD and OData queries
22+
- `client.query` -- query and search operations
23+
- `client.tables` -- table metadata, columns, and relationships
24+
2025
### Bulk Operations
2126
The SDK supports Dataverse's native bulk operations: Pass lists to `create()`, `update()` for automatic bulk processing, for `delete()`, set `use_bulk_delete` when passing lists to use bulk operation
2227

@@ -29,7 +34,7 @@ The SDK supports Dataverse's native bulk operations: Pass lists to `create()`, `
2934
### Import
3035
```python
3136
from azure.identity import (
32-
InteractiveBrowserCredential,
37+
InteractiveBrowserCredential,
3338
ClientSecretCredential,
3439
CertificateCredential,
3540
AzureCliCredential
@@ -56,41 +61,38 @@ client = DataverseClient("https://yourorg.crm.dynamics.com", credential)
5661
#### Create Records
5762
```python
5863
# Single record
59-
account_ids = client.create("account", {"name": "Contoso Ltd", "telephone1": "555-0100"})
60-
account_id = account_ids[0]
64+
account_id = client.records.create("account", {"name": "Contoso Ltd", "telephone1": "555-0100"})
6165

6266
# Bulk create (uses CreateMultiple API automatically)
6367
contacts = [
6468
{"firstname": "John", "lastname": "Doe"},
6569
{"firstname": "Jane", "lastname": "Smith"}
6670
]
67-
contact_ids = client.create("contact", contacts)
71+
contact_ids = client.records.create("contact", contacts)
6872
```
6973

7074
#### Read Records
7175
```python
7276
# Get single record by ID
73-
account = client.get("account", account_id, select=["name", "telephone1"])
77+
account = client.records.get("account", account_id, select=["name", "telephone1"])
7478

75-
# Query with filter
76-
pages = client.get(
79+
# Query with filter (paginated)
80+
for page in client.records.get(
7781
"account",
7882
select=["accountid", "name"], # select is case-insensitive (automatically lowercased)
7983
filter="statecode eq 0", # filter must use lowercase logical names (not transformed)
80-
top=100
81-
)
82-
for page in pages:
84+
top=100,
85+
):
8386
for record in page:
8487
print(record["name"])
8588

8689
# Query with navigation property expansion (case-sensitive!)
87-
pages = client.get(
90+
for page in client.records.get(
8891
"account",
8992
select=["name"],
9093
expand=["primarycontactid"], # Navigation properties are case-sensitive!
91-
filter="statecode eq 0" # Column names must be lowercase logical names
92-
)
93-
for page in pages:
94+
filter="statecode eq 0", # Column names must be lowercase logical names
95+
):
9496
for account in page:
9597
contact = account.get("primarycontactid", {})
9698
print(f"{account['name']} - {contact.get('fullname', 'N/A')}")
@@ -99,28 +101,27 @@ for page in pages:
99101
#### Update Records
100102
```python
101103
# Single update
102-
client.update("account", account_id, {"telephone1": "555-0200"})
104+
client.records.update("account", account_id, {"telephone1": "555-0200"})
103105

104106
# Bulk update (broadcast same change to multiple records)
105-
client.update("account", [id1, id2, id3], {"industry": "Technology"})
107+
client.records.update("account", [id1, id2, id3], {"industry": "Technology"})
106108
```
107109

108110
#### Delete Records
109111
```python
110112
# Single delete
111-
client.delete("account", account_id)
113+
client.records.delete("account", account_id)
112114

113115
# Bulk delete (uses BulkDelete API)
114-
client.delete("account", [id1, id2, id3], use_bulk_delete=True)
116+
client.records.delete("account", [id1, id2, id3], use_bulk_delete=True)
115117
```
116118

117119
### SQL Queries
118120

119121
SQL queries are **read-only** and support limited SQL syntax. A single SELECT statement with optional WHERE, TOP (integer literal), ORDER BY (column names only), and a simple table alias after FROM is supported. But JOIN and subqueries may not be. Refer to the Dataverse documentation for the current feature set.
120122

121123
```python
122-
# Basic SQL query
123-
results = client.query_sql(
124+
results = client.query.sql(
124125
"SELECT TOP 10 accountid, name FROM account WHERE statecode = 0"
125126
)
126127
for record in results:
@@ -132,22 +133,22 @@ for record in results:
132133
#### Create Custom Tables
133134
```python
134135
# Create table with columns (include customization prefix!)
135-
table_info = client.create_table(
136-
table_schema_name="new_Product",
137-
columns={
136+
table_info = client.tables.create(
137+
"new_Product",
138+
{
138139
"new_Code": "string",
139140
"new_Price": "decimal",
140141
"new_Active": "bool",
141-
"new_Quantity": "int"
142-
}
142+
"new_Quantity": "int",
143+
},
143144
)
144145

145146
# With solution assignment and custom primary column
146-
table_info = client.create_table(
147-
table_schema_name="new_Product",
148-
columns={"new_Code": "string", "new_Price": "decimal"},
149-
solution_unique_name="MyPublisher",
150-
primary_column_schema_name="new_ProductCode"
147+
table_info = client.tables.create(
148+
"new_Product",
149+
{"new_Code": "string", "new_Price": "decimal"},
150+
solution="MyPublisher",
151+
primary_column="new_ProductCode",
151152
)
152153
```
153154

@@ -165,32 +166,101 @@ Types on the same line map to the same exact format under the hood
165166
#### Manage Columns
166167
```python
167168
# Add columns to existing table (must include customization prefix!)
168-
client.create_columns("new_Product", {
169+
client.tables.add_columns("new_Product", {
169170
"new_Category": "string",
170-
"new_InStock": "bool"
171+
"new_InStock": "bool",
171172
})
172173

173174
# Remove columns
174-
client.delete_columns("new_Product", ["new_Category"])
175+
client.tables.remove_columns("new_Product", ["new_Category"])
175176
```
176177

177178
#### Inspect Tables
178179
```python
179180
# Get single table information
180-
table_info = client.get_table_info("new_Product")
181+
table_info = client.tables.get("new_Product")
181182
print(f"Logical name: {table_info['table_logical_name']}")
182183
print(f"Entity set: {table_info['entity_set_name']}")
183184

184185
# List all tables
185-
tables = client.list_tables()
186+
tables = client.tables.list()
186187
for table in tables:
187188
print(table)
188189
```
189190

190191
#### Delete Tables
191192
```python
192-
# Delete custom table
193-
client.delete_table("new_Product")
193+
client.tables.delete("new_Product")
194+
```
195+
196+
### Relationship Management
197+
198+
#### Create One-to-Many Relationship
199+
```python
200+
from PowerPlatform.Dataverse.models.metadata import (
201+
LookupAttributeMetadata,
202+
OneToManyRelationshipMetadata,
203+
Label,
204+
LocalizedLabel,
205+
CascadeConfiguration,
206+
)
207+
from PowerPlatform.Dataverse.common.constants import CASCADE_BEHAVIOR_REMOVE_LINK
208+
209+
lookup = LookupAttributeMetadata(
210+
schema_name="new_DepartmentId",
211+
display_name=Label(
212+
localized_labels=[LocalizedLabel(label="Department", language_code=1033)]
213+
),
214+
)
215+
216+
relationship = OneToManyRelationshipMetadata(
217+
schema_name="new_Department_Employee",
218+
referenced_entity="new_department",
219+
referencing_entity="new_employee",
220+
referenced_attribute="new_departmentid",
221+
cascade_configuration=CascadeConfiguration(
222+
delete=CASCADE_BEHAVIOR_REMOVE_LINK,
223+
),
224+
)
225+
226+
result = client.tables.create_one_to_many_relationship(lookup, relationship)
227+
print(f"Created lookup field: {result['lookup_schema_name']}")
228+
```
229+
230+
#### Create Many-to-Many Relationship
231+
```python
232+
from PowerPlatform.Dataverse.models.metadata import ManyToManyRelationshipMetadata
233+
234+
relationship = ManyToManyRelationshipMetadata(
235+
schema_name="new_employee_project",
236+
entity1_logical_name="new_employee",
237+
entity2_logical_name="new_project",
238+
)
239+
240+
result = client.tables.create_many_to_many_relationship(relationship)
241+
print(f"Created: {result['relationship_schema_name']}")
242+
```
243+
244+
#### Convenience Method for Lookup Fields
245+
```python
246+
result = client.tables.create_lookup_field(
247+
referencing_table="new_order",
248+
lookup_field_name="new_AccountId",
249+
referenced_table="account",
250+
display_name="Account",
251+
required=True,
252+
)
253+
```
254+
255+
#### Query and Delete Relationships
256+
```python
257+
# Get relationship metadata
258+
rel = client.tables.get_relationship("new_Department_Employee")
259+
if rel:
260+
print(f"Found: {rel['SchemaName']}")
261+
262+
# Delete relationship
263+
client.tables.delete_relationship(result["relationship_id"])
194264
```
195265

196266
### File Operations
@@ -220,7 +290,7 @@ from PowerPlatform.Dataverse.core.errors import (
220290
from PowerPlatform.Dataverse.client import DataverseClient
221291

222292
try:
223-
client.get("account", "invalid-id")
293+
client.records.get("account", "invalid-id")
224294
except HttpError as e:
225295
print(f"HTTP {e.status_code}: {e.message}")
226296
print(f"Error code: {e.code}")
@@ -262,6 +332,7 @@ except ValidationError as e:
262332
7. **Always include customization prefix** for custom tables/columns
263333
8. **Use lowercase** - Generally using lowercase input won't go wrong, except for custom table/column naming
264334
9. **Test in non-production environments** first
335+
10. **Use named constants** - Import cascade behavior constants from `PowerPlatform.Dataverse.common.constants`
265336

266337
## Additional Resources
267338

0 commit comments

Comments
 (0)