次の方法で共有


プログラムを使ってクエリで作業項目を取得する

Azure DevOps Services

クエリを使用した作業項目のフェッチは、Azure DevOps Services の一般的なシナリオです。 この記事では、REST API またはクライアント ライブラリを使用してプログラムでこのシナリオ.NET実装する方法について説明します。

ヒント

この記事の後半でAIを使用してこのタスクを支援することができます。また、作業を開始するには、Azure DevOps MCP ServerでAIサポートを有効にする方法を参照してください。

前提条件

カテゴリ 必要条件
Azure DevOps - 組織
- 作業項目を含むプロジェクトへのアクセス
認証 以下のいずれかの方法を選択します。
- Microsoft Entra ID 認証 (対話型アプリに推奨)
- サービス プリンシパル認証 (自動化に推奨)
- Managed Identity authentication (Azure ホステッド アプリに推奨)
- 個人用アクセス トークン (テスト用)
開発環境 C# 開発環境。 Visual Studio

重要

より安全なMicrosoft Entraトークンを、リスクの高い個人アクセス トークンよりも使用することを検討してください。 詳細については、「 PAT 使用量の削減」を参照してください認証ガイダンスを確認して、ニーズに適した認証メカニズムを選択します。

認証オプション

この記事では、さまざまなシナリオに合わせて複数の認証方法を示します。

ユーザー操作を使用する運用アプリケーションの場合は、Microsoft Entra ID認証を使用します。

<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" />

自動化されたシナリオ、CI/CD パイプライン、およびサーバー アプリケーションの場合:

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

Azure サービス (Functions、App Service など) で実行されているアプリケーションの場合:

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

個人用アクセス トークン認証

開発とテストのシナリオの場合:

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

C# のコード例

次の例は、さまざまな認証方法を使用して作業項目を取得する方法を示しています。

例 1: Microsoft Entra ID認証 (対話型)

この例で使用する VssAadCredential クラスには、Microsoft.VisualStudio.Services.InteractiveClient パッケージが必要であり、.NET Framework を対象としています。 .NET Core/.NET 5 以降のアプリケーションの場合は、Example 2 (サービス プリンシパル) または Example 3 (マネージド ID) に示されている MSAL ベースのアプローチと、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"]}");
        }
    }
}

例 2: サービス プリンシパル認証 (自動化されたシナリオ)

// 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>();
            }
        }
    }
}

例 3: マネージド ID 認証 (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>();
            }
        }
    }
}

例 4: 個人用アクセス トークン認証

// 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>();
            }
        }
    }
}

使用例

次の例では、各認証クラスを呼び出す方法を示します。

Microsoft Entra ID認証の使用 (対話型)

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

サービス プリンシパル認証の使用 (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"]}");
        }
    }
}

マネージド ID 認証の使用 (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;
    }
}

個人用アクセス トークン認証の使用 (開発/テスト)

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"]}");
        }
    }
}

ベスト プラクティス

認証

  • ユーザー サインインを使用した対話型アプリケーションのMicrosoft Entra IDの使用
  • 自動化されたシナリオ、CI/CD パイプライン、およびサーバー アプリケーションにサービス プリンシパルを使用する
  • Azure サービス (Functions、App Service、VM) で実行されているアプリケーションのマネージド ID を使用する
  • 運用環境では個人用アクセス トークンを避けます。開発とテストにのみ使用する
  • ソースコードに資格情報をハードコードしないでください。その代わりに、環境変数またはAzure Key Vaultを使用する。
  • 実行時間の長いアプリケーションの資格情報のローテーションを実装する
  • 適切なスコープを有効にする - 作業項目クエリには、Azure DevOpsで適切な読み取りアクセス許可が必要です

エラー処理

  • 一時的な障害に対して指数バックオフを使用して再試行ロジックを実装する
  • デバッグと監視のためにエラーを適切にログに記録する
  • 認証エラーやネットワーク タイムアウトなどの特定の例外を処理する
  • 実行時間の長い操作にキャンセル トークンを使用する

[パフォーマンス]

  • 複数のアイテムに対してクエリを実行するときのバッチ作業項目の取得
  • 大規模なデータセットに対して TOP 句を使用してクエリ結果を制限する
  • 頻繁にアクセスされるデータをキャッシュ して API 呼び出しを減らす
  • 適切なフィールドを使用して データ転送を最小限に抑える

クエリ最適化

  • パフォーマンスを向上させるには、SELECT * の代わりに特定のフィールド名を使用します
  • サーバーで結果をフィルター処理するための適切な WHERE 句を追加する
  • ユース ケースに合わせて結果を適切に並べ替えます
  • 大規模な結果セットのクエリ制限と改ページを検討する

トラブルシューティング

認証の問題

  • Microsoft Entra ID 認証エラー - ユーザーが適切なアクセス許可を持ち、Azure DevOpsにサインインしていることを確認します
  • サービス プリンシパル認証エラー - クライアント ID、シークレット、テナント ID が正しいことを確認します。Azure DevOpsでサービス プリンシパルのアクセス許可を確認する
  • 管理 ID 認証エラー - Azure リソースでマネージド ID が有効になっており、適切なアクセス許可があることを確認します
  • PAT 認証エラー - トークンが有効であり、適切なスコープ (作業項目へのアクセスにvso.work ) があることを確認します
  • トークンの有効期限 - PAT の有効期限が切れているかどうかを確認し、必要に応じて新しい PAT を生成します

クエリに関する問題

  • WIQL 構文が無効です - 作業項目クエリ言語の構文が正しいことを確認してください
  • プロジェクト名エラー - プロジェクト名が存在し、スペルが正しいことを確認する
  • フィールド名エラー - 正しいシステム フィールド名を使用します (例: System.IdSystem.Title)

一般的な例外

  • VssUnauthorizedException - 認証資格情報とアクセス許可を確認する
  • ArgumentException - 必要なすべてのパラメーターが指定され、有効であることを確認する
  • HttpRequestException - ネットワーク接続とサービスの可用性を確認する

パフォーマンスの問題

  • 低速クエリ - 適切な WHERE 句を追加し、結果セットを制限する
  • メモリ使用量 - 大量の結果セットをバッチで処理する
  • レート制限 - 指数バックオフを使用して再試行ロジックを実装する

AI を使用してプログラムで作業項目のクエリを実行する

エージェント モードで Azure DevOps MCP Server が AI エージェントに接続されている場合は、自然言語プロンプトを使用して作業項目のクエリを実行するためのコードを生成できます。

Task プロンプトの例
クエリ コードを生成する Write C# code to query all active bugs assigned to me in Azure DevOps using the .NET client libraries with Microsoft Entra authentication
REST API クエリ Create a REST API call to fetch work items from Azure DevOps using a WIQL query with a personal access token
保存されたクエリを実行する Show me how to use the Azure DevOps .NET client to run a saved query and retrieve work item details including custom fields
CSV にエクスポート Build a .NET app that fetches work items from Azure DevOps and exports them to CSV using managed identity authentication
エリア パスでフィルターする Write C# code to query work items under area path <Contoso\Backend> that were modified in the last 7 days
大量の結果をページ分割する Show me how to query Azure DevOps work items in batches of 200 using the .NET client libraries with proper pagination

エージェント モードと MCP サーバーでは自然言語が使用されるため、これらのプロンプトを調整したり、フォローアップの質問をして結果を絞り込むことができます。