Ya hemos cubierto los fundamentos de cómo consultar datos con Retrieve y RetrieveMultiple. Ahora es momento de profundizar en QueryExpression para consultas más complejas, y conocer FetchXML como alternativa para escenarios específicos.
Objetivos de aprendizaje
- Construir consultas complejas con múltiples filtros
- Usar LinkEntity para hacer JOINs entre entidades
- Implementar paginación para grandes conjuntos de datos
- Conocer cuándo usar FetchXML en lugar de QueryExpression
Filtros complejos con QueryExpression
Las consultas reales rara vez tienen un solo filtro. Normalmente necesitas combinar múltiples condiciones con lógica AND y OR.
QueryExpression query = new QueryExpression("opportunity");
query.ColumnSet = new ColumnSet("name", "estimatedvalue", "closeprobability");
// Condiciones en Criteria usan AND por defecto
query.Criteria.AddCondition("statecode", ConditionOperator.Equal, 0); // Abierta
query.Criteria.AddCondition("estimatedvalue", ConditionOperator.GreaterEqual, 10000);
Esto es equivalente a un WHERE statecode = 0 AND estimatedvalue >= 10000. Pero ¿qué pasa cuando necesitas un OR?
Combinando AND y OR
Para lógica más compleja, creas FilterExpressions adicionales y los anidas:
QueryExpression query = new QueryExpression("opportunity");
query.ColumnSet = new ColumnSet("name", "estimatedvalue", "closeprobability");
// Filtro principal (AND por defecto)
query.Criteria.FilterOperator = LogicalOperator.And;
query.Criteria.AddCondition("statecode", ConditionOperator.Equal, 0);
// Subfiltro OR: oportunidades grandes O de alta probabilidad
FilterExpression filtroOr = new FilterExpression(LogicalOperator.Or);
filtroOr.AddCondition("estimatedvalue", ConditionOperator.GreaterEqual, 50000);
filtroOr.AddCondition("closeprobability", ConditionOperator.GreaterEqual, 80);
query.Criteria.AddFilter(filtroOr);
// Resultado: statecode = 0 AND (estimatedvalue >= 50000 OR closeprobability >= 80)
JOINs con LinkEntity
LinkEntity permite hacer JOINs entre entidades relacionadas:
QueryExpression query = new QueryExpression("contact");
query.ColumnSet = new ColumnSet("fullname", "emailaddress1");
// JOIN a la cuenta padre
LinkEntity linkCuenta = query.AddLink(
"account", // Entidad a unir
"parentcustomerid", // Campo en contact que referencia
"accountid", // Campo primario en account
JoinOperator.Inner // Tipo de JOIN
);
// Traer campos de la cuenta
linkCuenta.Columns = new ColumnSet("name", "telephone1");
linkCuenta.EntityAlias = "cuenta";
// Filtrar por datos de la cuenta
linkCuenta.LinkCriteria.AddCondition("industrycode", ConditionOperator.Equal, 6);
EntityCollection resultados = service.RetrieveMultiple(query);
Los campos de entidades unidas vienen como AliasedValue:
foreach (Entity contacto in resultados.Entities)
{
string nombreContacto = contacto.GetAttributeValue<string>("fullname");
// Campo de la cuenta unida
AliasedValue avNombreCuenta = contacto.GetAttributeValue<AliasedValue>("cuenta.name");
string nombreCuenta = (string)avNombreCuenta.Value;
trace.Trace($"{nombreContacto} - {nombreCuenta}");
}
Paginación de resultados
Dataverse tiene un límite de 5000 registros por consulta. Para conjuntos de datos grandes, necesitas paginar:
QueryExpression query = new QueryExpression("contact");
query.ColumnSet = new ColumnSet("fullname");
query.PageInfo = new PagingInfo
{
PageNumber = 1,
Count = 500 // Registros por página
};
List<Entity> todosLosContactos = new List<Entity>();
while (true)
{
EntityCollection pagina = service.RetrieveMultiple(query);
todosLosContactos.AddRange(pagina.Entities);
if (!pagina.MoreRecords)
break;
query.PageInfo.PageNumber++;
query.PageInfo.PagingCookie = pagina.PagingCookie;
}
trace.Trace($"Total contactos: {todosLosContactos.Count}");
El PagingCookie es importante para eficiencia. Permite a Dataverse continuar desde donde quedó en lugar de recalcular la página desde el inicio.
FetchXML: la alternativa XML
FetchXML es un lenguaje de consulta basado en XML específico de Dataverse. Es más potente que QueryExpression en algunos aspectos, particularmente para agregaciones:
string fetchXml = @"
<fetch aggregate='true'>
<entity name='opportunity'>
<attribute name='estimatedvalue' aggregate='sum' alias='total' />
<attribute name='ownerid' groupby='true' alias='vendedor' />
<filter>
<condition attribute='statecode' operator='eq' value='0' />
</filter>
</entity>
</fetch>";
EntityCollection resultados = service.RetrieveMultiple(new FetchExpression(fetchXml));
foreach (Entity resultado in resultados.Entities)
{
AliasedValue avVendedor = resultado.GetAttributeValue<AliasedValue>("vendedor");
AliasedValue avTotal = resultado.GetAttributeValue<AliasedValue>("total");
EntityReference vendedor = (EntityReference)avVendedor.Value;
Money total = (Money)avTotal.Value;
trace.Trace($"Vendedor {vendedor.Name}: ${total.Value}");
}
Las agregaciones (SUM, COUNT, AVG, MIN, MAX) y agrupaciones (GROUP BY) solo están disponibles en FetchXML. QueryExpression no las soporta.
Otro caso de uso es la portabilidad: el mismo FetchXML funciona en Web API, JavaScript, Power Automate, y otros contextos. Si tienes una consulta compleja que necesitas ejecutar desde múltiples lugares, FetchXML te permite reutilizarla.
QueryExpression vs FetchXML
En la práctica, la mayoría de los desarrolladores prefieren QueryExpression para código C# porque tienes IntelliSense, tipado fuerte, y es más fácil de debuggear. Usa FetchXML cuando necesites agregaciones o cuando la consulta venga de una fuente externa.
Puntos clave
- Usa FilterExpression anidados para combinar AND y OR
- LinkEntity hace JOINs; los campos vienen como AliasedValue
- Siempre pagina consultas que pueden devolver muchos registros
- FetchXML es útil para agregaciones y portabilidad
- QueryExpression es preferible para código C# por el tipado y IntelliSense