Condividi tramite


Recuperare elementi di lavoro con query programmaticamente

Azure DevOps Services

Il recupero di elementi di lavoro tramite query è uno scenario comune nei servizi di Azure DevOps. Questo articolo illustra come implementare questo scenario a livello di codice usando le API REST o le librerie client .NET.

Suggerimento

È possibile usare l'intelligenza artificiale per facilitare questa attività più avanti in questo articolo, oppure vedere Abilitare l'assistenza AI con Azure DevOps MCP Server per iniziare.

Prerequisiti

Categoria Requisiti
Azure DevOps - Un'organizzazione
- Accesso a un progetto con elementi di lavoro
Autenticazione Scegliere una delle seguenti modalità:
- Microsoft Entra ID authentication (consigliato per le app interattive)
- Autenticazione dell'entità servizio (consigliata per l'automazione)
- Autenticazione dell'identità gestita (consigliata per le app ospitate Azure)
- Token di accesso personale (per i test)
Ambiente di sviluppo Un ambiente di sviluppo C#. È possibile usare Visual Studio

Importante

Prendere in considerazione l'uso dei token Microsoft Entra più sicuri rispetto ai token personali di accesso ad alto rischio. Per altre informazioni, vedere Ridurre l'utilizzo di PAT. Esaminare le indicazioni per l'autenticazione per scegliere il meccanismo di autenticazione appropriato per le proprie esigenze.

Opzioni di autenticazione

Questo articolo illustra più metodi di autenticazione in base a scenari diversi:

Per le applicazioni di produzione con interazione con l'utente, usare Microsoft Entra ID per l'autenticazione.

<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.232.1" />
<PackageReference Include="Microsoft.VisualStudio.Services.InteractiveClient" Version="19.232.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.2" />

Per scenari automatizzati, pipeline CI/CD e applicazioni server:

<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.232.1" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.2" />

Per le applicazioni in esecuzione nei servizi Azure (Funzioni, Servizio app e così via):

<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.232.1" />
<PackageReference Include="Azure.Identity" Version="1.13.1" />

Autenticazione con token di accesso personale

Per scenari di sviluppo e test:

<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.232.1" />

Esempi di codice C#

Negli esempi seguenti viene illustrato come recuperare elementi di lavoro usando metodi di autenticazione diversi.

Esempio 1: autenticazione Microsoft Entra ID (interattiva)

Annotazioni

La classe VssAadCredential usata in questo esempio richiede il pacchetto Microsoft.VisualStudio.Services.InteractiveClient ed è destinata a .NET Framework. Per le applicazioni .NET Core/.NET 5+, usare l'metodo basato su MSAL mostrato in Example 2 (Service Principal) o Example 3 (Managed Identity) con VssOAuthAccessTokenCredential.

// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Microsoft.VisualStudio.Services.InteractiveClient  
// Microsoft.Identity.Client
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;

public class EntraIdQueryExecutor
{
    private readonly Uri uri;

    /// <summary>
    /// Initializes a new instance using Microsoft Entra ID authentication.
    /// </summary>
    /// <param name="orgName">Your Azure DevOps organization name</param>
    public EntraIdQueryExecutor(string orgName)
    {
        this.uri = new Uri("https://dev.azure.com/" + orgName);
    }

    /// <summary>
    /// Execute a WIQL query using Microsoft Entra ID authentication.
    /// </summary>
    /// <param name="project">The name of your project within your organization.</param>
    /// <returns>A list of WorkItem objects representing all the open bugs.</returns>
    public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
    {
        // Use Microsoft Entra ID authentication
        var credentials = new VssAadCredential();
        var wiql = new Wiql()
        {
            Query = "SELECT [System.Id], [System.Title], [System.State] " +
                    "FROM WorkItems " +
                    "WHERE [Work Item Type] = 'Bug' " +
                    "AND [System.TeamProject] = @project " +
                    "AND [System.State] <> 'Closed' " +
                    "ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
        };

        using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
        {
            try
            {
                var result = await httpClient.QueryByWiqlAsync(wiql, project).ConfigureAwait(false);
                var ids = result.WorkItems.Select(item => item.Id).ToArray();

                if (ids.Length == 0)
                {
                    return Array.Empty<WorkItem>();
                }

                var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
                return await httpClient.GetWorkItemsAsync(ids, fields, result.AsOf).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error querying work items: {ex.Message}");
                return Array.Empty<WorkItem>();
            }
        }
    }

    /// <summary>
    /// Print the results of the work item query.
    /// </summary>
    public async Task PrintOpenBugsAsync(string project)
    {
        var workItems = await this.QueryOpenBugsAsync(project).ConfigureAwait(false);
        Console.WriteLine($"Query Results: {workItems.Count} items found");

        foreach (var workItem in workItems)
        {
            Console.WriteLine($"{workItem.Id}\t{workItem.Fields["System.Title"]}\t{workItem.Fields["System.State"]}");
        }
    }
}

Esempio 2: Autenticazione dell'entità servizio (scenari automatizzati)

// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Microsoft.Identity.Client
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;

public class ServicePrincipalQueryExecutor
{
    private readonly Uri uri;
    private readonly string clientId;
    private readonly string clientSecret;
    private readonly string tenantId;

    /// <summary>
    /// Initializes a new instance using Service Principal authentication.
    /// </summary>
    /// <param name="orgName">Your Azure DevOps organization name</param>
    /// <param name="clientId">Service principal client ID</param>
    /// <param name="clientSecret">Service principal client secret</param>
    /// <param name="tenantId">Microsoft Entra tenant ID</param>
    public ServicePrincipalQueryExecutor(string orgName, string clientId, string clientSecret, string tenantId)
    {
        this.uri = new Uri($"https://dev.azure.com/{orgName}");
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.tenantId = tenantId;
    }

    /// <summary>
    /// Execute a WIQL query using Service Principal authentication.
    /// </summary>
    public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
    {
        // Acquire token using Service Principal
        var app = ConfidentialClientApplicationBuilder
            .Create(this.clientId)
            .WithClientSecret(this.clientSecret)
            .WithAuthority($"https://login.microsoftonline.com/{this.tenantId}")
            .Build();

        var scopes = new[] { "https://app.vssps.visualstudio.com/.default" };
        var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();

        var credentials = new VssOAuthAccessTokenCredential(result.AccessToken);
        var wiql = new Wiql()
        {
            Query = "SELECT [System.Id], [System.Title], [System.State] " +
                    "FROM WorkItems " +
                    "WHERE [Work Item Type] = 'Bug' " +
                    "AND [System.TeamProject] = @project " +
                    "AND [System.State] <> 'Closed' " +
                    "ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
        };

        using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
        {
            try
            {
                var queryResult = await httpClient.QueryByWiqlAsync(wiql, project).ConfigureAwait(false);
                var ids = queryResult.WorkItems.Select(item => item.Id).ToArray();

                if (ids.Length == 0)
                {
                    return Array.Empty<WorkItem>();
                }

                var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
                return await httpClient.GetWorkItemsAsync(ids, fields, queryResult.AsOf).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error querying work items: {ex.Message}");
                return Array.Empty<WorkItem>();
            }
        }
    }
}

Esempio 3: Autenticazione dell'identità gestita (app ospitate Azure)

// NuGet packages:
// Microsoft.TeamFoundationServer.Client
// Azure.Identity
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Identity;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;

public class ManagedIdentityQueryExecutor
{
    private readonly Uri uri;

    /// <summary>
    /// Initializes a new instance using Managed Identity authentication.
    /// </summary>
    /// <param name="orgName">Your Azure DevOps organization name</param>
    public ManagedIdentityQueryExecutor(string orgName)
    {
        this.uri = new Uri($"https://dev.azure.com/{orgName}");
    }

    /// <summary>
    /// Execute a WIQL query using Managed Identity authentication.
    /// </summary>
    public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
    {
        // Use Managed Identity to acquire token
        var credential = new DefaultAzureCredential();
        var tokenRequestContext = new TokenRequestContext(new[] { "https://app.vssps.visualstudio.com/.default" });
        var tokenResult = await credential.GetTokenAsync(tokenRequestContext);

        var credentials = new VssOAuthAccessTokenCredential(tokenResult.Token);
        var wiql = new Wiql()
        {
            Query = "SELECT [System.Id], [System.Title], [System.State] " +
                    "FROM WorkItems " +
                    "WHERE [Work Item Type] = 'Bug' " +
                    "AND [System.TeamProject] = @project " +
                    "AND [System.State] <> 'Closed' " +
                    "ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
        };

        using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
        {
            try
            {
                var queryResult = await httpClient.QueryByWiqlAsync(wiql, project).ConfigureAwait(false);
                var ids = queryResult.WorkItems.Select(item => item.Id).ToArray();

                if (ids.Length == 0)
                {
                    return Array.Empty<WorkItem>();
                }

                var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
                return await httpClient.GetWorkItemsAsync(ids, fields, queryResult.AsOf).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error querying work items: {ex.Message}");
                return Array.Empty<WorkItem>();
            }
        }
    }
}

Esempio 4: Autenticazione del token di accesso personale

// NuGet package: Microsoft.TeamFoundationServer.Client
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;

public class PatQueryExecutor
{
    private readonly Uri uri;
    private readonly string personalAccessToken;

    /// <summary>
    /// Initializes a new instance using Personal Access Token authentication.
    /// </summary>
    /// <param name="orgName">Your Azure DevOps organization name</param>
    /// <param name="personalAccessToken">Your Personal Access Token</param>
    public PatQueryExecutor(string orgName, string personalAccessToken)
    {
        this.uri = new Uri("https://dev.azure.com/" + orgName);
        this.personalAccessToken = personalAccessToken;
    }

    /// <summary>
    /// Execute a WIQL query using Personal Access Token authentication.
    /// </summary>
    /// <param name="project">The name of your project within your organization.</param>
    /// <returns>A list of WorkItem objects representing all the open bugs.</returns>
    public async Task<IList<WorkItem>> QueryOpenBugsAsync(string project)
    {
        var credentials = new VssBasicCredential(string.Empty, this.personalAccessToken);
        var wiql = new Wiql()
        {
            Query = "SELECT [System.Id], [System.Title], [System.State] " +
                    "FROM WorkItems " +
                    "WHERE [Work Item Type] = 'Bug' " +
                    "AND [System.TeamProject] = @project " +
                    "AND [System.State] <> 'Closed' " +
                    "ORDER BY [System.State] ASC, [System.ChangedDate] DESC",
        };

        using (var httpClient = new WorkItemTrackingHttpClient(this.uri, new VssCredentials(credentials)))
        {
            try
            {
                var result = await httpClient.QueryByWiqlAsync(wiql, project).ConfigureAwait(false);
                var ids = result.WorkItems.Select(item => item.Id).ToArray();

                if (ids.Length == 0)
                {
                    return Array.Empty<WorkItem>();
                }

                var fields = new[] { "System.Id", "System.Title", "System.State", "System.CreatedDate" };
                return await httpClient.GetWorkItemsAsync(ids, fields, result.AsOf).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error querying work items: {ex.Message}");
                return Array.Empty<WorkItem>();
            }
        }
    }
}

Esempi di utilizzo

Gli esempi seguenti illustrano come chiamare ogni classe di autenticazione.

Uso dell'autenticazione Microsoft Entra ID (interattiva)

class Program
{
    static async Task Main(string[] args)
    {
        var executor = new EntraIdQueryExecutor("your-organization-name");
        await executor.PrintOpenBugsAsync("your-project-name");
    }
}

Uso dell'autenticazione dell'entità servizio (scenari CI/CD)

class Program
{
    static async Task Main(string[] args)
    {
        // These values should come from environment variables or Azure Key Vault
        var clientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID");
        var clientSecret = Environment.GetEnvironmentVariable("AZURE_CLIENT_SECRET");
        var tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");
        
        var executor = new ServicePrincipalQueryExecutor("your-organization-name", clientId, clientSecret, tenantId);
        var workItems = await executor.QueryOpenBugsAsync("your-project-name");
        
        Console.WriteLine($"Found {workItems.Count} open bugs via automation");
        foreach (var item in workItems)
        {
            Console.WriteLine($"Bug {item.Id}: {item.Fields["System.Title"]}");
        }
    }
}

Uso dell'autenticazione dell'identità gestita (Azure Functions/App Service)

public class WorkItemQueryFunction
{
    private readonly ILogger<WorkItemQueryFunction> _logger;

    public WorkItemQueryFunction(ILogger<WorkItemQueryFunction> logger)
    {
        _logger = logger;
    }

    [Function("QueryOpenBugs")]
    public async Task<HttpResponseData> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req)
    {
        var executor = new ManagedIdentityQueryExecutor("your-organization-name");
        var workItems = await executor.QueryOpenBugsAsync("your-project-name");

        var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
        await response.WriteAsJsonAsync(new { 
            Count = workItems.Count,
            Items = workItems.Select(wi => new { 
                Id = wi.Id, 
                Title = wi.Fields["System.Title"],
                State = wi.Fields["System.State"]
            })
        });
        return response;
    }
}

Uso dell'autenticazione del token di accesso personale (sviluppo/test)

class Program
{
    static async Task Main(string[] args)
    {
        var pat = Environment.GetEnvironmentVariable("AZURE_DEVOPS_PAT"); // Never hardcode PATs
        var executor = new PatQueryExecutor("your-organization-name", pat);
        var workItems = await executor.QueryOpenBugsAsync("your-project-name");
        
        Console.WriteLine($"Found {workItems.Count} open bugs");
        foreach (var item in workItems)
        {
            Console.WriteLine($"Bug {item.Id}: {item.Fields["System.Title"]}");
        }
    }
}

Procedure consigliate

Autenticazione

  • Usare Microsoft Entra ID per applicazioni interattive con accesso utente
  • Utilizzare i principali del servizio per scenari automatizzati, pipeline CI/CD e applicazioni per server
  • Utilizza identità gestite per applicazioni in esecuzione su servizi Azure (Funzioni, App Service, VMs)
  • Evitare token di accesso personali nell'ambiente di produzione; uso solo per lo sviluppo e il test
  • Non incorporare mai le credenziali nel codice sorgente; usare variabili di ambiente o Azure Key Vault
  • Implementare la rotazione delle credenziali per le applicazioni a esecuzione prolungata
  • Assicurarsi di propri ambiti - Le query degli elementi di lavoro richiedono autorizzazioni di lettura appropriate in Azure DevOps

Gestione degli errori

  • Implementare la logica di ripetizione dei tentativi con backoff esponenziale per gli errori temporanei
  • Registrare gli errori in modo appropriato per il debug e il monitoraggio
  • Gestire eccezioni specifiche , ad esempio errori di autenticazione e timeout di rete
  • Usare i token di annullamento per le operazioni a esecuzione prolungata

Prestazioni

  • Recupero di elementi di lavoro in batch durante l'esecuzione di query su più elementi
  • Limitare i risultati delle query usando la clausola TOP per set di dati di grandi dimensioni
  • Memorizzare nella cache i dati a cui si accede di frequente per ridurre le chiamate API
  • Usare i campi appropriati per ridurre al minimo il trasferimento dei dati

Ottimizzazione della query

  • Usare nomi di campo specifici anziché SELECT * per ottenere prestazioni migliori
  • Aggiungere clausole WHERE appropriate per filtrare i risultati sul server
  • Ordinare i risultati in modo appropriato per il caso d'uso
  • Considerare i limiti delle query e la paginazione per set di risultati di grandi dimensioni

Risoluzione dei problemi

Problemi di autenticazione

  • Microsoft Entra ID errori di autenticazione - Verificare che l'utente disponga delle autorizzazioni appropriate e che sia connesso a Azure DevOps
  • Errori di autenticazione del principale del servizio - Verificare che l'ID client, il segreto e l'ID tenant siano corretti; controllare le autorizzazioni del principale del servizio in Azure DevOps
  • Managed identity authentication failures - Assicurarsi che la risorsa Azure disponga di un'identità gestita abilitata e delle autorizzazioni corrette.
  • Errori di autenticazione PAT : verificare che il token sia valido e abbia ambiti appropriati (vso.work per l'accesso agli elementi di lavoro)
  • Scadenza del token : controllare se il token di accesso personale è scaduto e generarne uno nuovo, se necessario

Problemi di query

  • Sintassi WIQL non valida : verificare che la sintassi del linguaggio di query dell'elemento di lavoro sia corretta
  • Errori del nome del progetto: verificare che il nome del progetto esista e che sia stato digitato correttamente
  • Errori di nome campo : usare i nomi di campo di sistema corretti (ad esempio, System.Id, System.Title)

Eccezioni comuni

  • VssUnauthorizedException - Controllare le credenziali e le autorizzazioni di autenticazione
  • ArgumentException - Verificare che tutti i parametri obbligatori siano specificati e validi
  • HttpRequestException - Controllare la connettività di rete e la disponibilità del servizio

Problemi di prestazioni

  • Query "lente" - Aggiungere clausole WHERE appropriate e limitare i set di risultati
  • Utilizzo della memoria - Elaborare set di risultati di grandi dimensioni in batch
  • Limitazione della frequenza - Implementare la logica di ripetizione dei tentativi con backoff esponenziale

Usare l'intelligenza artificiale per eseguire query sugli elementi di lavoro programmaticamente

Se si dispone del Azure DevOps MCP Server connesso all'agente di intelligenza artificiale in modalità agente, è possibile usare i prompt del linguaggio naturale per generare codice per l'esecuzione di query sugli elementi di lavoro.

Attività Richiesta di esempio
Generare il codice della query Write C# code to query all active bugs assigned to me in Azure DevOps using the .NET client libraries with Microsoft Entra authentication
Query dell'API REST Create a REST API call to fetch work items from Azure DevOps using a WIQL query with a personal access token
Eseguire una query salvata Show me how to use the Azure DevOps .NET client to run a saved query and retrieve work item details including custom fields
Esporta in CSV Build a .NET app that fetches work items from Azure DevOps and exports them to CSV using managed identity authentication
Filtra in base al percorso dell'area Write C# code to query work items under area path <Contoso\Backend> that were modified in the last 7 days
Impaginare risultati di grandi dimensioni Show me how to query Azure DevOps work items in batches of 200 using the .NET client libraries with proper pagination

Annotazioni

La modalità agente e il server MCP usano il linguaggio naturale, quindi è possibile modificare queste richieste o porre domande di completamento per perfezionare i risultati.