← Files Microsoft DataverseARCHIVED FILE
skills/dv-data/references/sample-data-generation.md
6.74 KB · Oct 5, 2026 · 18:29 UTC
# Sample Data Generation
Generate and insert realistic sample records into any Dataverse table. Useful for development, demos, and testing.
**Use the Python SDK** (`client.records.create()`) — not raw `urllib` or `requests`.
## Agentic Flow
### Step 1: Confirm environment and count
Before creating anything, confirm:
- **Target environment** — run `pac auth list` to show the active environment
- **Record count** — default is **5 records** unless the user specifies otherwise
- **Table name** — get the logical name (e.g., `account`, `contact`, `cr123_customtable`)
### Step 2: Inspect the table schema
Use the SDK's `client.tables.list_columns()` (it reads EntityDefinitions/Attributes) to discover required columns and their types. Two non-obvious bits:
- **`filter="AttributeOf eq null"`** — without this, each lookup column returns a duplicated sub-attribute row (e.g. a `primarycontactid` Lookup plus a `_primarycontactid_value` pair), which makes the list twice as long and confuses downstream code.
- **`UserLocalizedLabel` is null on unlocalized columns** — dereference safely before reading `.Label`, otherwise custom tables without display names crash the loop.
```python
import os, sys
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_client
client = get_client("dv-data")
TABLE = "account" # or any other table logical name
# tables.list_columns reads EntityDefinitions/Attributes via the SDK — no urllib.
attrs = client.tables.list_columns(
TABLE,
select=["LogicalName", "AttributeType", "RequiredLevel", "DisplayName"],
filter="AttributeOf eq null",
)
for a in attrs:
dn = (a.get("DisplayName") or {}).get("UserLocalizedLabel")
label = dn["Label"] if dn else a["LogicalName"]
if a["RequiredLevel"]["Value"] == "ApplicationRequired":
print(f"REQUIRED {a['LogicalName']:30s} {a['AttributeType']:15s} {label}", flush=True)
```
Filter or group the raw `attrs` list however the task needs — don't assume the printed shape above; inline code downstream should consume `attrs` directly.
### Step 3: Generate realistic data (by AttributeType)
Use the schema from Step 2 to pick a generator per column. No separate script — the agent writes this inline per request so it matches the actual table.
| AttributeType | Generate |
|---|---|
| `String` / `Memo` | Realistic text based on column name (e.g., `name` -> company names) |
| `Integer` / `Decimal` / `Money` | Random values within `MinValue`/`MaxValue` |
| `Boolean` | Alternate `true`/`false` |
| `DateTime` | Recent dates in ISO 8601 format |
| `Picklist` / `Status` | Integer option values (e.g., `industrycode: 1`) |
| `Lookup` | **Skip by default** — only set if user provides valid record IDs |
| `Uniqueidentifier` (non-PK) | Skip — let Dataverse auto-generate |
### Step 4: Create the records inline via the SDK — schema-driven
This template is **table-agnostic by design**. It reads the `attrs` list from Step 2 and dispatches by `AttributeType` — no table-specific field names or hardcoded sample values baked in. Use it as a starting point; override/extend `fake()` when the table has domain-specific needs (e.g., look up real picklist option values, generate industry-appropriate company names for `account`, etc.).
```python
import os, sys, random, datetime
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_client
# get_client sets a plugin attribution context on the User-Agent header.
# Do not modify the context value — it is a closed schema for server-side
# telemetry (app/skill/agent). Never include secrets or PII.
client = get_client("dv-data")
TABLE = "account" # any table logical name from Step 1
COUNT = 5 # confirmed with user
# `attrs` = the list returned by Step 2's EntityDefinitions query (re-run Step 2 if needed)
SKIP_TYPES = {"Lookup", "Uniqueidentifier", "EntityName", "State", "Status", "Owner", "Customer"}
def fake(attr, i):
"""Context-based value by AttributeType + PII-safe heuristics on column name."""
name, t = attr["LogicalName"], attr["AttributeType"]
if t in ("String", "Memo"):
if "email" in name: return f"user{i}@example.com"
if any(s in name for s in ("phone", "telephone", "fax")): return f"555-01{i:02d}"
if "url" in name or "website" in name: return f"https://example.com/{name}/{i}"
return f"Sample {name} {i}"
if t in ("Integer", "BigInt"): return random.randint(1, 1000)
if t in ("Decimal", "Double", "Money"): return round(random.uniform(1, 10_000), 2)
if t == "Boolean": return bool(i % 2)
if t == "DateTime":
d = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=i)
return d.isoformat(timespec="seconds").replace("+00:00", "Z")
if t in ("Picklist", "Status"):
return 1 # placeholder — for real picklists, look up valid OptionSet values first
return None # skip Lookup, Uniqueidentifier, and anything unhandled
required = [a for a in attrs
if a["RequiredLevel"]["Value"] == "ApplicationRequired"
and a["AttributeType"] not in SKIP_TYPES]
records = []
for i in range(COUNT):
rec = {}
for a in required:
v = fake(a, i)
if v is not None:
rec[a["LogicalName"]] = v
records.append(rec)
# CreateMultiple for count >= 10, individual creates otherwise.
if COUNT >= 10:
ids = client.records.create(TABLE, records)
print(f"Created {len(ids)} records via CreateMultiple", flush=True)
else:
ids = [client.records.create(TABLE, r) for r in records]
print(f"Created {len(ids)} records individually", flush=True)
env_url = os.environ["DATAVERSE_URL"].rstrip("/") # get_client already called load_env()
print(f"View: {env_url}/main.aspx?pagetype=entitylist&etn={TABLE}", flush=True)
```
**Why schema-driven and not a hardcoded 5-account template:** a template that bakes in `account`-shaped columns (`name`, `telephone1`, `revenue`, `numberofemployees`) biases the agent toward copy-paste-then-hack whenever the user asks for `contact` or `cr123_project` records. The `fake()` function above dispatches per-attribute so the same snippet produces correct fields for any table. Override `fake()` when you need domain-specific values — e.g. real company names for `account.name`, valid status-reason integers for a custom picklist.
## Safety Rules for Sample Data
- **Always confirm** the target environment and record count
- Use `.example.com` domains for emails — never real domains
- Use `555-01xx` phone numbers — obviously fake
- Skip lookup fields unless user explicitly asks
- Skip system fields: `createdon`, `modifiedon`, `ownerid`, `statecode`, `statuscode`
SHA-256: d066313d84a00681ee9960587a925b884048f68b8c67a2015e291f60eca8ef6e