| name | bc-dataverse-entity-generator |
| description | Automatically generate AL table objects from Dataverse entities using ALTPGen, with automatic table ID allocation and proper file organization. Use when user requests to generate, create, or import a Dataverse entity into Business Central. Triggers when user mentions generating entities, creating tables from Dataverse, or importing Dataverse data structures. |
Business Central Dataverse Entity Generator
Overview
This skill automates the generation of AL table objects from Microsoft Dataverse entities using the ALTPGen tool. It handles automatic table ID allocation, runs the generation script, and organizes the generated files according to project conventions with proper BCS prefix naming.
When to Use This Skill
Use this skill when:
- User requests to generate an AL table from a Dataverse entity
- Importing a new Dataverse entity (e.g., account, contact, opportunity)
- Creating integration tables for Dataverse synchronization
- User mentions "generate entity", "create from Dataverse", or "import [entity] from Dataverse"
- Setting up new Dataverse entity mappings
Prerequisites
Before running this skill, ensure:
- ALTPGen tool is available in VS Code AL extension
- Dataverse environment is accessible
- Azure AD App Registration is configured (ClientId in script)
- User has authentication permissions to Dataverse environment
- Generate-Entity.ps1 script exists in
altpgen/ folder
Core Workflow
Step 1: Identify Entity and Find Next Available Table ID
Determine the Dataverse entity name:
- User specifies entity (e.g., "account", "contact", "opportunity")
- Common entities: account, contact, lead, opportunity, systemuser, etc.
- Entity names are lowercase and match Dataverse schema names
Find the next available table ID:
# Search for all existing table IDs in src/Table/
Get-ChildItem "src/Table/*.al" |
ForEach-Object {
$content = Get-Content $_.FullName -Raw
if ($content -match 'table\s+(\d+)') {
[int]$matches[1]
}
} |
Sort-Object |
Select-Object -Last 1
ID Allocation Logic:
- Scan all .al files in
src/Table/ folder
- Extract table ID numbers using regex:
table\s+(\d+)
- Find the highest ID currently in use
- Next available ID = highest ID + 1
- Standard range: 70000-70999 (check app.json for actual range)
Example:
Existing tables: 70000, 70001, 70002, 70003, 70006, 70007
Next available ID: 70008
Step 2: Validate Script Configuration
Before running the generation script, verify:
- ALTPGen Path - Check VS Code extension version:
$alExtension = Get-ChildItem "$env:USERPROFILE\.vscode\extensions" -Filter "ms-dynamics-smb.al-*" | Select-Object -First 1
$altpgenPath = Join-Path $alExtension.FullName "bin\win32\altpgen\altpgen.exe"
-
Update Generate-Entity.ps1 if needed:
- Verify
$AltpgenPath points to correct AL extension version
- Confirm
$ServiceUri matches target Dataverse environment
- Ensure
$ClientId is configured
-
Verify project structure:
app.json exists in root
.alpackages/ folder exists (for symbol cache)
src/Table/ target folder exists
Step 3: Run Entity Generation Script
Execute the PowerShell script with automatic parameters:
# Navigate to script directory
cd "altpgen"
# Run with Execution Policy bypass (if needed)
powershell -ExecutionPolicy Bypass -File .\Generate-Entity.ps1 `
-Entity "<entity-name>" `
-BaseId <next-table-id>
Example:
powershell -ExecutionPolicy Bypass -File .\Generate-Entity.ps1 -Entity account -BaseId 70008
What happens during execution:
- Script authenticates with Dataverse (opens browser for OAuth)
- ALTPGen connects to Dataverse environment
- Retrieves entity schema (fields, relationships, metadata)
- Generates AL table object with CDS integration
- Creates integration table in
src/ root folder
- Returns success message
Expected Output:
Running ALTPGen for entity 'account' with BaseId 70008...
Authenticating Dataverse using ServiceUri=https://org28f3df88.crm.dynamics.com/
Generating tables for entity: account
ALTPGen completed successfully ✅
Step 4: Locate Generated Files
After successful generation, find the new files in the project root:
# Generated files appear in src/ folder with pattern:
# - CDSaccount.Table.al (CDS integration table)
# - Related integration files
# Search for newly created files (last 2 minutes)
Get-ChildItem "src/" -Filter "*.al" |
Where-Object { $_.LastWriteTime -gt (Get-Date).AddMinutes(-2) }
Typical generated files:
CDSaccount.Table.al - Main CDS integration table
- May include related codeunits or pages depending on entity
Step 5: Move Files to Proper Location
Move generated files from src/ to src/Table/ folder:
# Find and move generated CDS tables
Get-ChildItem "src/" -Filter "CDS*.Table.al" |
ForEach-Object {
$destination = "src/Table/$($_.Name)"
Move-Item $_.FullName $destination -Force
Write-Host "Moved: $($_.Name) -> src/Table/"
}
Manual verification:
- Check if files exist in
src/ root
- Move
*.Table.al files to src/Table/
- Move related codeunits to
src/Codeunit/ if generated
- Move pages to
src/Page/ if generated
Step 6: Apply BCS Naming Convention
Rename files and update object names to follow BCS prefix standard:
File naming pattern:
- Original:
CDSaccount.Table.al
- Target:
BCSCDSAccount.Table.al
Object naming pattern:
- Original:
table 70008 "CDS account"
- Target:
table 70008 "BCS CDS Account"
Implementation:
# For each generated file in src/Table/
$file = Get-Item "src/Table/CDSaccount.Table.al"
# Read content
$content = Get-Content $file.FullName -Raw
# Update table name in code
$content = $content -replace 'table (\d+) "CDS ([^"]+)"', 'table $1 "BCS CDS $2"'
# Save with updated content
Set-Content $file.FullName $content
# Rename file with BCS prefix
$newName = "BCSCDSAccount.Table.al"
Rename-Item $file.FullName $newName
Apply to code using AL editing:
For each generated file:
-
Update table declaration:
// Before:
table 70008 "CDS account"
// After:
table 70008 "BCS CDS Account"
-
Rename file:
CDSaccount.Table.al → BCSCDSAccount.Table.al
-
Update internal references if any (table extensions, pages)
Step 7: Check for Existing Table and Merge New Fields
Before treating this as a completely new table, check if a table for this entity already exists:
Detection Strategy:
# Search for existing CDS table with same entity name
$entityPattern = "table\s+\d+\s+`"BCS CDS $Entity`""
$existingTable = Get-ChildItem "src/Table/*.al" |
Where-Object { (Get-Content $_.FullName -Raw) -match $entityPattern }
if ($existingTable) {
Write-Host "⚠️ Found existing table: $($existingTable.Name)"
Write-Host "Will merge new fields from generated table"
}
If existing table is found:
- Read both files - existing table and newly generated table
- Extract new fields - Compare field lists, identify new fields in generated version
- Merge fields - Add new fields to existing table preserving original structure
- Preserve customizations - Keep any custom procedures, triggers, or modifications
- Delete generated file - Remove the newly generated file after merge
Field Merging Process:
// Example: Existing table has fields 1-20
// Generated table has fields 1-25 (includes 5 new fields)
// Goal: Add fields 21-25 to existing table
// 1. Read existing table structure
$existingContent = Get-Content "src/Table/BCSCDSAccount.Table.al" -Raw
// 2. Parse generated table to find new fields
$generatedContent = Get-Content "src/Table/BCSCDSAccount.Table.al.new" -Raw
// 3. Extract field definitions that don't exist in original
// 4. Insert new fields before the closing 'fields' section
// 5. Preserve all keys, triggers, and procedures from original
Manual merge steps when automatic merge is complex:
- Open both files side by side in VS Code
- Identify new fields in generated version (check field numbers and ExternalName)
- Copy new field definitions to existing table
- Adjust field numbers if there are conflicts
- Keep custom code from original table (triggers, procedures)
- Delete generated duplicate after successful merge
When to update vs regenerate:
| Scenario | Action |
|---|
| Entity never generated before | Use generated table as-is |
| Entity exists, schema unchanged | Skip generation, keep existing |
| Entity exists, new fields in Dataverse | Merge new fields into existing |
| Entity exists, fields removed in Dataverse | Manual review required |
| Entity exists, field types changed | Manual review and testing required |
Step 8: Update Permission Set with New Table
After confirming the table is ready (new or merged), update the permission set file to include permissions for the table:
Locate permission set file:
# Common permission set file patterns in BC projects
$permissionSetFile = Get-ChildItem "*.PermissionSet.al" | Select-Object -First 1
if ($permissionSetFile) {
Write-Host "Found permission set: $($permissionSetFile.Name)"
} else {
Write-Host "⚠️ No permission set file found. Will create entry."
}
Add table permissions:
For a new table like 70008 "BCS CDS Account", add these permissions:
// In BCSBCSPermission.PermissionSet.al or similar file
permissionset 50100 "BCS Permissions"
{
Assignable = true;
Caption = 'BCS Custom Permissions';
Permissions =
// ... existing permissions ...
tabledata "BCS CDS Account" = RIMD, // Read, Insert, Modify, Delete
table "BCS CDS Account" = X; // Execute
}
Automated permission addition:
# Read permission set file
$permFile = "BCSBCSPermission.PermissionSet.al"
$content = Get-Content $permFile -Raw
# Find the Permissions section
if ($content -match 'Permissions\s*=\s*\n') {
# Add new table permission after existing permissions
$newPermission = " tabledata `"BCS CDS $Entity`" = RIMD,`n table `"BCS CDS $Entity`" = X,"
# Insert before closing semicolon of Permissions block
$content = $content -replace '(Permissions\s*=\s*\n.*?)(;)', "`$1`n$newPermission`$2"
Set-Content $permFile $content
Write-Host "✅ Added permissions for BCS CDS $Entity"
}
Permission levels explained:
- R (Read) - Can read table data
- I (Insert) - Can create new records
- M (Modify) - Can update existing records
- D (Delete) - Can delete records
- X (Execute) - Can execute table object (metadata access)
Verify permission set syntax:
After adding permissions, ensure:
- Comma placement is correct (comma after each permission except last)
- Table names match exactly (including BCS prefix)
- No duplicate entries exist
- Permission set builds without errors
Alternative: Use AL permission generation tool:
# Use VS Code AL command to regenerate all permissions
# Ctrl+Shift+P → "AL: Generate permission set containing current extension objects"
This auto-generates permissions for ALL objects in the extension. Review the generated permissions and keep only what's needed.
Step 9: Validate and Build
After moving, renaming, merging fields, and updating permissions:
- Verify file structure:
src/Table/
└── BCSCDSAccount.Table.al ✅ Correct location and name
- Build project to check for errors:
# Use AL build command
Ctrl+Shift+P → "AL: Build"
# Or run terminal command if available
-
Check for compilation errors:
- Missing dependencies
- Incorrect object IDs
- Naming conflicts
- Field type issues
-
Review generated fields:
- Check field mappings from Dataverse
- Verify TableRelation properties
- Confirm ExternalName matches Dataverse schema
- Validate DataClassification settings
Step 10: Final Permission Set Validation
After the build succeeds, verify permission set changes:
# Review permission set file for new entries
Get-Content "BCSBCSPermission.PermissionSet.al" | Select-String "BCS CDS"
Verify permissions were added correctly:
- Check that tabledata permissions include RIMD
- Verify table permissions include X
- Ensure no syntax errors (commas, semicolons)
- Confirm table name matches exactly
If permissions are missing or incomplete, regenerate:
# Use AL command to regenerate all permissions
Ctrl+Shift+P → "AL: Generate permission set containing current extension objects"
Review the updated permission set and commit changes.
This creates/updates permission set files with access rights for the new CDS table.
Complete Automation Example
Here's a complete automated workflow:
# 1. Find next available table ID
$existingTables = Get-ChildItem "src/Table/*.al" |
ForEach-Object {
if ((Get-Content $_.FullName -Raw) -match 'table\s+(\d+)') {
[int]$matches[1]
}
}
$nextId = ($existingTables | Sort-Object | Select-Object -Last 1) + 1
Write-Host "Next available table ID: $nextId"
# 2. Run generation script
cd altpgen
powershell -ExecutionPolicy Bypass -File .\Generate-Entity.ps1 -Entity "account" -BaseId $nextId
# 3. Wait for completion and user authentication
Read-Host "Press Enter after authentication completes..."
# 4. Move files to proper location
cd ..
$generatedFiles = Get-ChildItem "src/" -Filter "CDS*.Table.al"
foreach ($file in $generatedFiles) {
Move-Item $file.FullName "src/Table/" -Force
Write-Host "Moved: $($file.Name)"
}
# 5. Apply BCS naming convention
cd "src/Table"
$cdsFiles = Get-ChildItem "CDS*.Table.al"
foreach ($file in $cdsFiles) {
# Update content
$content = Get-Content $file.FullName -Raw
$content = $content -replace 'table (\d+) "CDS ([^"]+)"', 'table $1 "BCS CDS $2"'
Set-Content $file.FullName $content
# Rename file
$newName = $file.Name -replace '^CDS', 'BCSCDS'
Rename-Item $file.FullName $newName -Force
Write-Host "Renamed: $($file.Name) -> $newName"
}
cd ../..
# 6. Check for existing table and merge new fields if needed
$entityName = "account"
$existingTable = Get-ChildItem "src/Table/*.al" |
Where-Object { (Get-Content $_.FullName -Raw) -match "table\s+\d+\s+`"BCS CDS $entityName`"" -and $_.Name -notmatch "\.new$" }
if ($existingTable -and (Test-Path "src/Table/BCSCDS$entityName.Table.al.new")) {
Write-Host "⚠️ Found existing table: $($existingTable.Name)"
Write-Host "Merging new fields from generated table..."
# Manual merge required - open both files for comparison
Write-Host "Please review and merge new fields manually"
Write-Host " Existing: $($existingTable.FullName)"
Write-Host " Generated: src/Table/BCSCDS$entityName.Table.al"
}
# 7. Update permission set with new table
$permFile = Get-ChildItem "*.PermissionSet.al" | Select-Object -First 1
if ($permFile) {
Write-Host "Updating permission set: $($permFile.Name)"
$permContent = Get-Content $permFile.FullName -Raw
$tableName = "BCS CDS Account"
# Check if permission already exists
if ($permContent -notmatch [regex]::Escape($tableName)) {
# Add permissions for the new table
$newPerms = @"
tabledata "$tableName" = RIMD,
table "$tableName" = X,
"@
# Find the Permissions section and add before the closing
$permContent = $permContent -replace '(Permissions\s*=\s*\n)(.*?)(;)', "`$1`$2`n$newPerms`n`$3"
Set-Content $permFile.FullName $permContent
Write-Host "✅ Added permissions for $tableName"
} else {
Write-Host "✅ Permissions already exist for $tableName"
}
}
Write-Host "✅ Entity generation complete! Next steps:"
Write-Host "1. Review generated table in src/Table/"
Write-Host "2. Check permission set updates"
Write-Host "3. Build project (Ctrl+Shift+P → AL: Build)"
Write-Host "4. Verify permissions in permission set file"
Write-Host "5. Test Dataverse synchronization"
User Interaction Patterns
Pattern 1: Simple Entity Request
User: "Generate the account entity from Dataverse"
Agent Response:
Generating Dataverse entity 'account':
1. Finding next available table ID... ✅ Next ID: 70008
2. Running Generate-Entity.ps1 script...
[Executing PowerShell script]
3. Please authenticate in the browser window that opens
[Waiting for user authentication]
4. Moving generated files to src/Table/
5. Applying BCS naming convention
6. Checking for existing tables... No existing table found
7. Updating permission set...
✅ Added permissions for "BCS CDS Account"
8. Files created:
- src/Table/BCSCDSAccount.Table.al
- BCSBCSPermission.PermissionSet.al (updated)
9. Building project to validate...
✅ Account entity generated successfully!
Next: Review the table and test synchronization.
Pattern 2: Updating Existing Entity
User: "Regenerate the account entity to get new fields from Dataverse"
Agent Response:
Regenerating Dataverse entity 'account':
1. Finding existing table... ✅ Found: BCSCDSAccount.Table.al (Table 70008)
2. Running Generate-Entity.ps1 to get latest schema...
[Executing PowerShell script]
3. Analyzing field differences...
- Found 5 new fields in Dataverse
- Found 2 fields with modified properties
4. Merging new fields into existing table:
✅ Added field 21: "Account Category Code"
✅ Added field 22: "Preferred Service"
✅ Added field 23: "Last Interaction Date"
✅ Added field 24: "Customer Size Code"
✅ Added field 25: "Industry Vertical"
5. Preserving custom code:
✅ Kept custom trigger: OnBeforeInsert
✅ Kept custom procedure: ValidateAccountData
6. Permission set already includes this table ✅
7. Building project to validate...
✅ Account table updated with new Dataverse fields!
Next: Test field synchronization with Dataverse.
Pattern 3: Multiple Entities
User: "Generate contact and opportunity entities"
Agent Response:
Generating multiple Dataverse entities:
Entity 1: contact
- Table ID: 70008
- Permissions: ✅ Added
- Status: ✅ Generated and organized
Entity 2: opportunity
- Table ID: 70009
- Permissions: ✅ Added
- Status: ✅ Generated and organized
All entities created in src/Table/ with BCS prefix.
Permission set updated with both tables.
Next: Build project and configure synchronization.
Pattern 4: Error Handling
User: "Generate the lead entity"
Agent Response (if authentication fails):
⚠️ Generation failed: Authentication timeout
Please ensure:
1. Azure AD App Registration is configured
2. You have access to Dataverse environment
3. Redirect URI is registered in Azure AD
Would you like to:
- Retry generation
- Check script configuration
- View authentication troubleshooting steps
Common Entity Names Reference
| Dataverse Entity | AL Table Name Pattern | Business Context |
|---|
| account | BCS CDS Account | Companies/Organizations |
| contact | BCS CDS Contact | People/Individuals |
| lead | BCS CDS Lead | Sales prospects |
| opportunity | BCS CDS Opportunity | Sales opportunities |
| quote | BCS CDS Quote | Sales quotes |
| salesorder | BCS CDS Sales Order | Sales orders |
| invoice | BCS CDS Invoice | Sales invoices |
| product | BCS CDS Product | Products/Items |
| pricelevel | BCS CDS Price Level | Price lists |
| systemuser | BCS CDS System User | CRM users |
| team | BCS CDS Team | User teams |
| organization | BCS CDS Organization | Organization settings |
Troubleshooting
Issue: ALTPGen Tool Not Found
Error: "ALTPGen tool not found: [path]"
Solution:
- Find AL extension version:
Get-ChildItem "$env:USERPROFILE\.vscode\extensions" -Filter "ms-dynamics-smb.al-*"
- Update
$AltpgenPath in Generate-Entity.ps1
- Verify altpgen.exe exists at the path
Issue: Authentication Fails
Error: Authentication timeout or denied
Solution:
- Check Azure AD App Registration permissions
- Verify Redirect URI is registered
- Ensure user has Dataverse access
- Try re-running with fresh authentication
Issue: Table ID Conflict
Error: "Table 70008 already exists"
Solution:
- Re-scan for latest available ID
- Manually verify no uncommitted tables exist
- Use higher ID if necessary
- Check for table extensions that might conflict
Issue: Files Not Generated
Error: Script completes but no files created
Solution:
- Check entity name is correct (lowercase, no spaces)
- Verify Dataverse connection is successful
- Check console output for ALTPGen errors
- Ensure entity exists in Dataverse environment
Issue: Permission Set Syntax Error
Error: "Expected ';' after permissions"
Solution:
- Review permission set file for comma/semicolon issues
- Ensure each permission line ends with comma except the last
- Check for missing closing brace or semicolon
- Verify table name is enclosed in quotes correctly
- Use AL formatter to fix syntax issues
Issue: Duplicate Table Fields After Merge
Error: "Field 21 already exists" after merging
Solution:
- Manually review field numbers in both tables
- Adjust new field numbers to avoid conflicts
- Check for renamed fields (same ExternalName, different AL name)
- Remove duplicate fields before building
- Consider regenerating table from scratch if heavily modified
Best Practices
- Always check for next available ID - Don't hardcode table IDs
- Check for existing tables first - Avoid duplicate entity tables
- Merge carefully - When updating existing tables, preserve custom code
- Authenticate before starting - Ensure Dataverse access is working
- Move files immediately - Don't leave generated files in src/ root
- Update permissions automatically - Include permission updates in workflow
- Build after generation - Validate AL code compiles correctly
- Review field mappings - Check that Dataverse fields map correctly
- Test synchronization - Verify data flows between BC and Dataverse
- Document custom changes - If you modify generated tables, document why
- Version control - Commit permission set updates with table changes
- Backup before merge - Keep a copy of existing table before merging new fields
Integration with Other Skills
This skill works alongside:
- bc-tooltip-manager: Add tooltips to generated CDS table page fields
- AL build tools: Compile and validate generated code
- AL testing: Create test codeunits for CDS synchronization
- Permission generation: Create permission sets for CDS tables
Expected Outcomes
After running this skill successfully:
✅ New AL table object created in src/Table/
✅ File follows BCS naming convention
✅ Table ID automatically allocated
✅ Object name includes "BCS CDS" prefix
✅ Fields mapped from Dataverse entity
✅ Permission set updated with table access
✅ Project builds without errors
✅ Ready for Dataverse synchronization configuration
Next Steps After Generation
- Review the generated table structure - Check fields and mappings
- Verify permission set updates - Confirm tabledata and table permissions
- Build project (Ctrl+Shift+P → AL: Build)
- Test existing table merges - If fields were merged, validate all fields work
- Create corresponding page if needed (list/card)
- Configure integration mapping in setup codeunits
- Set up synchronization jobs for the entity
- Test data flow between BC and Dataverse
- Add business logic in event subscribers if needed
- Commit permission changes to version control
Quick Reference Commands
# Find next table ID
(Get-ChildItem "src/Table/*.al" | ForEach-Object { if ((Get-Content $_.FullName -Raw) -match 'table\s+(\d+)') { [int]$matches[1] } } | Sort-Object | Select-Object -Last 1) + 1
# Check for existing entity table
Get-ChildItem "src/Table/*.al" | Where-Object { (Get-Content $_.FullName -Raw) -match 'BCS CDS account' }
# Run generation
cd altpgen; powershell -ExecutionPolicy Bypass -File .\Generate-Entity.ps1 -Entity "account" -BaseId 70008
# Move files
Move-Item "src/CDS*.Table.al" "src/Table/" -Force
# Apply BCS prefix to files
Get-ChildItem "src/Table/CDS*.al" | Rename-Item -NewName { $_.Name -replace '^CDS', 'BCSCDS' }
# Update permission set (check for existing permissions first)
Get-Content "BCSBCSPermission.PermissionSet.al" | Select-String "BCS CDS Account"
# Build project
Ctrl+Shift+P → "AL: Build"