1. Introduction to Polymorphic Lookups
Polymorphic lookups (also called “party list” fields) in Dataverse allow a single lookup field to reference multiple entity types. This powerful feature enables flexible relationship modeling where a field can point to different tables depending on context.
Key Characteristics
✔ Single field stores references to multiple entities
✔ Resolves to the correct record type at runtime
✔ Used in core Dataverse features like Activities and Notes
✔ Supports custom entities beyond system tables
Common Use Cases
- Assigning tasks to users, teams, or roles
- Document ownership by accounts or contacts
- Generic commenting systems
- Multi-entity relationship tracking
2. Native vs. Custom Polymorphic Lookups
A. Native Polymorphic Fields
These come preconfigured in standard Dataverse tables:
Field | Supported Entities |
---|---|
Owner | User, Team |
Regarding | Account, Contact, Opportunity, Custom Entities |
Customer | Account, Contact |
B. Custom Polymorphic Lookups
You can create your own by:
- Setting Target Record Types in the lookup definition
- Using the
EntityReference
data type programmatically
3. Implementing Custom Polymorphic Lookups
Step 1: Create the Field
# PowerShell using Pac CLI
Add-CrmCustomField `
-EntityLogicalName "new_customentity" `
-FieldName "new_polymorphiclookup" `
-DisplayName "Related Record" `
-FieldType Lookup `
-Targets "account","contact","systemuser"
Step 2: Configure via UI
- Navigate to Maker Portal → Tables → Your Table → Columns
- Create a Lookup column
- Under Target record types, select multiple entities
Step 3: Programmatic Access
// C# SDK Example
var entityRef = new EntityReference(
logicalName: "account", // or "contact", etc.
id: Guid.Parse("00000000-0000-0000-0000-000000000001"));
entity["new_polymorphiclookup"] = entityRef;
4. Querying Polymorphic Data
FetchXML Example
<fetch>
<entity name="task">
<attribute name="subject"/>
<attribute name="regardingobjectid"/>
<filter>
<condition
attribute="regardingobjectid"
operator="eq"
value="00000000-0000-0000-0000-000000000001" />
</filter>
</entity>
</fetch>
Web API Query
GET /api/data/v9.2/tasks?$select=subject,_regardingobjectid_value&
$filter=_regardingobjectid_value eq 00000000-0000-0000-0000-000000000001
Resolving References
// Client-side resolution
var regardingObj = Xrm.Page.getAttribute("regardingobjectid").getValue();
if (regardingObj) {
var entityType = regardingObj.entityType; // "accounts", "contacts", etc.
var id = regardingObj.id;
}
5. Advanced Implementation Patterns
Pattern 1: Dynamic UI Based on Reference Type
// Show/hide fields based on polymorphic selection
function onPolymorphicFieldChange() {
var lookup = Xrm.Page.getAttribute("new_polymorphiclookup").getValue();
if (lookup) {
switch(lookup.entityType) {
case "account":
Xrm.Page.ui.tabs.get("tab_account").setVisible(true);
break;
case "contact":
Xrm.Page.ui.tabs.get("tab_contact").setVisible(true);
break;
}
}
}
Pattern 2: Plugin Handling for Multiple Types
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var target = (Entity)context.InputParameters["Target"];
if (target.Contains("new_polymorphiclookup"))
{
var entityRef = (EntityReference)target["new_polymorphiclookup"];
switch(entityRef.LogicalName)
{
case "account":
// Account-specific logic
break;
case "contact":
// Contact-specific logic
break;
}
}
}
Pattern 3: Virtual Entity Resolution
// Resolve to different external systems
var entityRef = new EntityReference(
logicalName: GetEntityTypeFromExternalId(externalId),
id: GetLocalIdFromExternalId(externalId));
6. Performance Considerations
Optimization Strategies
Scenario | Recommendation |
---|---|
Large datasets | Add filtered views on lookup fields |
Complex joins | Pre-cache reference types |
Bulk operations | Disable plugins during import |
Indexing Guidance
✔ Standard polymorphic fields are automatically indexed
✔ Custom fields may need manual index creation
✔ Composite indexes improve join performance
7. Limitations and Workarounds
Key Limitations
- No custom UI for type selection (uses standard lookup control)
- Cannot enforce different relationships per type
- Advanced filtering requires careful query design
Workarounds
// Custom lookup dialog with type filtering
function openCustomLookup() {
var options = {
entityTypes: ["account", "contact"],
defaultViewId: "00000000-0000-0000-0000-000000000001"
};
Xrm.Navigation.openLookup(options);
}
8. Security Implications
Access Control
✔ Field-level security applies uniformly
✔ Record sharing follows referenced entity permissions
✔ Auditing tracks all reference changes
Best Practices
- Validate reference types in plugins
- Implement row-level security filters
- Monitor for broken references
9. Real-World Examples
Example 1: Service Ticket System
graph LR
A[Service Ticket] --> B[Related To]
B --> C[Account]
B --> D[Contact]
B --> E[Equipment]
Example 2: Document Management
graph TD
F[Document] --> G[Owned By]
G --> H[User]
G --> I[Team]
G --> J[Department]
Example 3: Project Tracking
graph BT
K[Project Task] --> L[Responsible Party]
L --> M[Employee]
L --> N[Vendor]
L --> O[Contractor]
10. Troubleshooting Guide
Issue | Diagnosis | Solution |
---|---|---|
Lookup shows wrong type | Incorrect resolution | Clear browser cache |
Query returns no results | Missing join condition | Use link-entity in FetchXML |
Plugin fails on reference | Null check missing | Validate EntityReference exists |
Performance degradation | Missing index | Create custom index |
11. Future Enhancements
Expected Improvements
- Enhanced lookup dialogs with type filtering
- Strongly-typed SDK support
- Graph API integration
Community Solutions
✔ PCF controls for better UI
✔ Open-source type resolvers