-
Notifications
You must be signed in to change notification settings - Fork 14
PrimaryNameProvider
The PrimaryNameProvider is a key component in XrmUnitTest that determines how entity primary name attributes are resolved. This is essential for the framework to properly populate EntityReference names when working with the in-memory fake CRM server.
XrmUnitTest provides two implementations of the IPrimaryNameProvider interface:
- PrimaryNameViaFieldProvider (Default) - Uses a field on the early bound entity class to determine the primary name attribute
- PrimaryNameViaNonStandardNamesProvider - Uses a dictionary mapping of entity logical names to primary attribute names
This provider is the default and is compatible with entities generated by the Early Bound Generator, or equivalent tools that include a PrimaryNameAttribute field. (If a different field name is used, this can be configured to work with this name as well)
When using the Early Bound Generator, generated entity classes include a static field that identifies the primary name attribute. For example:
public partial class Account : Microsoft.Xrm.Sdk.Entity
{
public const string EntityLogicalName = "account";
public const string PrimaryIdAttribute = "accountid";
public const string PrimaryNameAttribute = "name"; // ← This field is used
// ... rest of class
}The PrimaryNameViaFieldProvider reads this PrimaryNameAttribute field at runtime to determine which attribute contains the entity's display name.
By default, this provider is enabled. You can explicitly configure it in your App.config:
<appSettings>
<!-- Set to true to use PrimaryNameViaFieldProvider (default: true) -->
<add key="CrmEntities.TypesContainPrimaryAttributeName" value="true" />
<!-- The name of the field containing the primary attribute name (default: "PrimaryNameAttribute") -->
<add key="CrmEntities.PrimaryNameAttributeName" value="PrimaryNameAttribute" />
</appSettings>- Your entity classes must be generated with a tool that includes the
PrimaryNameAttributefield - The Early Bound Generator includes this field by default
- Standard CrmSvcUtil may not include this field without customization
Here's a complete example of what an entity looks like when generated with the Early Bound Generator:
namespace DLaB.Xrm.Entities
{
/// <summary>
/// Business that represents a customer or potential customer.
/// </summary>
[Microsoft.Xrm.Sdk.Client.EntityLogicalNameAttribute("account")]
public partial class Account : Microsoft.Xrm.Sdk.Entity
{
public static class Fields
{
public const string AccountId = "accountid";
public const string Name = "name";
public const string AccountNumber = "accountnumber";
// ... other fields
}
public const string EntityLogicalName = "account";
public const string PrimaryIdAttribute = "accountid";
public const string PrimaryNameAttribute = "name";
public const int EntityTypeCode = 1;
// ... properties and methods
}
}This provider uses a dictionary to map entity logical names to their primary attribute names. It's useful when:
- You cannot generate your entities with the Early Bound Generator
- You're using entities generated by standard CrmSvcUtil/PAC Model Builder
The provider looks up the entity's logical name in a configured dictionary. If not found, it falls back to using EntityHelper.GetPrimaryFieldInfo() to attempt automatic detection.
To use this provider, you need to:
- Disable the field-based provider:
<appSettings>
<!-- Set to false to use PrimaryNameViaNonStandardNamesProvider -->
<add key="CrmEntities.TypesContainPrimaryAttributeName" value="false" />
</appSettings>- Configure entity mappings:
<appSettings>
<!-- Map entity logical names to their primary name attributes -->
<!-- Format: entitylogicalname1:primaryattribute1|entitylogicalname2:primaryattribute2 -->
<add key="CrmEntities.NonStandardAttributeNamesByEntity"
value="account:name|contact:fullname|customentity:customname" />
</appSettings>Here's a complete example for a project with custom entities:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<!-- Use dictionary-based provider -->
<add key="CrmEntities.TypesContainPrimaryAttributeName" value="false" />
<!-- Define primary names for standard and custom entities -->
<add key="CrmEntities.NonStandardAttributeNamesByEntity"
value="account:name|contact:fullname|lead:fullname|opportunity:name|new_customentity:new_entityname|new_project:new_projectname" />
</appSettings>
</configuration>You can also configure the provider programmatically in your test initialization:
using DLaB.Xrm.Client;
// In your test initialization class
[AssemblyInitialize]
public static void InitializeTestSettings(TestContext context)
{
// Disable field-based provider
AppConfig.CrmEntities.ContainPrimaryAttributeName = false;
// Configure non-standard names
AppConfig.CrmEntities.NonStandardAttributeNamesByEntity = new Dictionary<string, string>
{
{ "account", "name" },
{ "contact", "fullname" },
{ "lead", "fullname" },
{ "new_customentity", "new_entityname" },
{ "new_project", "new_projectname" }
};
}Some entities in Dynamics 365 do not have a primary name attribute (typically many-to-many relationship entities). Both providers handle these entities automatically by maintaining a list of known nameless entities.
The following entities are recognized as nameless by default:
aciviewmapperappconfigbusinessprocessflowinstanceconnectionroleassociationconnectionroleobjecttypecodeimportentitymappingimportloglookupmappingownermappingpicklistmappingprincipalobjectaccesstransformationmappingusersettings
If you have additional nameless entities, you can configure them:
<appSettings>
<!-- Add custom nameless entities (comma-separated) -->
<add key="CrmEntities.NamelessEntities" value="custom_jointable,custom_associationtable" />
</appSettings>Or programmatically:
AppConfig.CrmEntities.NamelessEntities = new List<string>
{
"custom_jointable",
"custom_associationtable"
};Use this decision tree to choose the appropriate provider:
Are you using an early bound generator that defines a primary name attribute?
├─ Yes → Use PrimaryNameViaFieldProvider (default)
│ └─ Verify your entities have the PrimaryNameAttribute field or you've updated the CrmEntities.PrimaryNameAttributeName config value to the new name
│
└─ No → Use PrimaryNameViaNonStandardNamesProvider
├─ Set CrmEntities.TypesContainPrimaryAttributeName = false
└─ Configure CrmEntities.NonStandardAttributeNamesByEntity
| Setting | Default | Description |
|---|---|---|
CrmEntities.TypesContainPrimaryAttributeName |
true |
When true, uses PrimaryNameViaFieldProvider. When false, uses PrimaryNameViaNonStandardNamesProvider
|
CrmEntities.PrimaryNameAttributeName |
"PrimaryNameAttribute" |
Name of the field containing the primary attribute name (only used when TypesContainPrimaryAttributeName is true) |
CrmEntities.NonStandardAttributeNamesByEntity |
"" |
Dictionary mapping entity logical names to primary name attributes (only used when TypesContainPrimaryAttributeName is false) |
CrmEntities.NamelessEntities |
(see above) | List of entity logical names that do not have a primary name attribute |