When working with Dataverse data in C#, QueryExpression is one of the most commonly used APIs, especially in plugins and server-side logic. While basic queries are straightforward, real-world scenarios often require joins, nested joins, and retrieving data from related tables.
In this post, we’ll focus on QueryExpression with LinkEntity, covering:
- How joins work conceptually
- Using LinkEntity
- Nested LinkEntity (joins on joins)
- Retrieving columns from linked entities
- Common mistakes and best practices
What Is QueryExpression?
QueryExpression is a C# object-based query API provided by the Dataverse SDK.
It allows developers to retrieve data programmatically using structured objects instead of XML or UI-based queries.
QueryExpression is commonly used in:
- Plugins
- Custom workflow activities
- SDK-based C# applications
- Server-side integrations
Unlike Advanced Search or FetchXML, QueryExpression is code-only.
Understanding Joins in QueryExpression (Quick Concept)
In QueryExpression, a LinkEntity represents a join between two Dataverse tables.
The JoinOperator defines how records are matched:
- Inner Join → Returns records only when related records exist
- Left Outer Join → Returns all primary records, even if related records do not exist
In most plugin scenarios, LeftOuter is preferred to avoid unintentionally excluding records.
Basic QueryExpression
var query = new QueryExpression("account")
{
ColumnSet = new ColumnSet("name", "accountnumber")
};
query.Criteria.AddCondition(
"statecode",
ConditionOperator.Equal,
0
);
var results = service.RetrieveMultiple(query);
This retrieves all active Accounts with selected columns.
Using LinkEntity (Joining Related Tables)
Real-world data usually spans multiple tables.
To retrieve related data, QueryExpression uses LinkEntity.
Example Scenario
Retrieve Accounts along with their Primary Contact details.
Example: Account → Contact (Single LinkEntity)
var query = new QueryExpression("account")
{
ColumnSet = new ColumnSet("name")
};
var contactLink = query.AddLink(
"contact",
"primarycontactid",
"contactid",
JoinOperator.LeftOuter
);
contactLink.EntityAlias = "primarycontact";
contactLink.Columns = new ColumnSet("fullname", "emailaddress1");
var results = service.RetrieveMultiple(query);
This joins:
- Account → Contact
- Retrieves selected contact columns
Accessing Columns from Linked Entities (Important)
Columns from linked entities are returned as AliasedValue.
foreach (var entity in results.Entities)
{
var accountName = entity.GetAttributeValue<string>("name");
var contactName = entity.GetAttributeValue<AliasedValue>(
"primarycontact.fullname"
)?.Value as string;
}
Common mistake:
Forgetting EntityAlias or not using AliasedValue.
Adding Filters on a Linked Entity
You can apply filters directly on the linked table.
contactLink.LinkCriteria.AddCondition(
"emailaddress1",
ConditionOperator.NotNull
);
This retrieves only accounts where the primary contact has an email address.
Nested LinkEntity (LinkEntity → LinkEntity)
In complex scenarios, you may need joins on joins.
Example Scenario
Retrieve Accounts → Primary Contact → Contact’s Parent Account
Example: Nested LinkEntity
var query = new QueryExpression("account")
{
ColumnSet = new ColumnSet("name")
};
var contactLink = query.AddLink(
"contact",
"primarycontactid",
"contactid",
JoinOperator.LeftOuter
);
contactLink.EntityAlias = "pc";
contactLink.Columns = new ColumnSet("fullname");
var parentAccountLink = contactLink.AddLink(
"account",
"parentcustomerid",
"accountid",
JoinOperator.LeftOuter
);
parentAccountLink.EntityAlias = "parentacc";
parentAccountLink.Columns = new ColumnSet("name");
var results = service.RetrieveMultiple(query);
Reading Data from Nested Linked Entities
var parentAccountName = entity.GetAttributeValue<AliasedValue>(
"parentacc.name"
)?.Value as string;
Each level of join:
- Requires its own alias
- Returns data as
AliasedValue
Important Rules to Remember
- Always set EntityAlias
- Always use AliasedValue for linked entity columns
- Prefer LeftOuter unless inner join is required
- Retrieve only required columns
- Avoid unnecessary joins for performance reasons
Conclusion
QueryExpression becomes truly powerful once you understand joins and LinkEntity usage.
With the right structure, you can:
- Retrieve complex relational data
- Keep logic inside C# code
- Avoid multiple Dataverse calls
Mastering LinkEntity and nested joins is essential for real-world Dynamics 365 development.
In the next post, we’ll build on this foundation by covering paging, ordering, and performance considerations in QueryExpression.