查询项目 API (VisualStudio.Extensibility)

重要

本文档指 VisualStudio.Extensibility 的项目查询 API 功能。 有关 Visual Studio SDK API 文档,请参阅 查询 Visual Studio SDK 的项目 API

VisualStudio.Extensibility 项目查询 API 允许从项目系统查询信息。 项目系统是 Visual Studio 组件的一部分,可帮助用户处理和维护项目、运行生成以生成结果和测试输出。

使用项目查询 API,可以:

  1. 与 Project Systems 一起工作
  2. 从项目检索数据
  3. 对项目进行更改

项目查询 API 可以执行的作的一些示例包括让你了解项目中包含的文件、项目引用的 NuGet 包、向项目添加新文件或调整项目属性。

在 Visual Studio 项目系统扩展性文档参考中查找有关项目系统的详细信息。

使用项目查询 API

本概述介绍使用项目查询 API 的顶级方案:

访问项目查询空间

需要获取 项目查询空间 对象的实例来查询项目系统。 此对象具有多个异步方法,用于查询或更新项目系统。 术语 项目查询空间 和术语 工作区 均表示相同的内容,并引用提供对项目所有数据的访问权限的对象。 本文档将始终使用 workspace

使用工作区扩展功能

内置于WorkspacesExtensibilityMicrosoft.VisualStudio.Extensibility的对象提供了一种简单且集成的方法来使用项目查询。

WorkspacesExtensibility workspace = this.Extensibility.Workspaces();

在项目系统中查询项目

对象 WorkspacesExtensibility 允许你查询单个项目,只要你有项目的 GUID。 通常有两个与项目关联的 GUID,一个表示项目类型,另一个唯一表示项目。 可以在解决方案文件中找到项目的唯一 GUID,或者可以从扩展中查询 Guid 属性,如下一部分所示。

IQueryResults<IProjectSnapshot> projects = await workspace.QueryProjectByGuidAsync(
    project => project.With(project => new { project.Id, project.Path }),
    knownGuid,
    cancellationToken);

指定要包含在查询结果中的项目参数

查询项目系统时,利用 With 子句来确定查询结果中包含哪些参数或元数据。 有几个有效的方法可以指定应包含哪些参数。

With 每个参数的条件

IQueryResults<IProjectSnapshot> allProjects = await workspace.QueryProjectsAsync(
    project => project.With(p => p.Name)
        .With(p => p.Path)
        .With(p => p.Guid)
        .With(p => p.Kind) // DTE.Project.Kind
        .With(p => p.Type) // VSHPROPID_ProjectType
        .With(p => p.TypeGuid) // VSHPROPID_TypeGuid
        .With(p => p.Capabilities),
    cancellationToken);

foreach (IProjectSnapshot project in allProjects)
{
    var projectGuid = project.Guid;
    // Checking whether 'Capabilities' property has been retrieved.
    // Otherwise, it can throw for projects which do not support it. (Like SQL projects)
    bool capabilities = project.PropertiesAvailableStatus.Capabilities;
}

单个 With 子句用于指定多个参数

还可以在单个 With 子句中指定多个所需参数。

IQueryResults<IProjectSnapshot> results = await workspace.QueryProjectsAsync(
    project => project.With(p => new { p.Path, p.Guid, p.Kind, p.Type, p.TypeGuid, p.Capabilities }),
    cancellationToken);

使用 WithRequired 子句的示例

使用 WithRequired 可确保只检索包含指定属性的项目。 例如,在以下示例中,仅选择了包含命名 information 文件的项目。

IQueryResults<IProjectSnapshot> results = await workspace.QueryProjectsAsync(
    project => project.With(p => new { p.Path, p.Guid })
    .WithRequired(p => p.Files.Where(f => f.FileName == "information")),
    cancellationToken);

筛选查询结果

可以通过两种方式应用条件筛选来限制查询结果:

Where 语句

不同的项目类型支持不同的功能集。 使用子 Where 句,可以筛选支持特定功能的项目。 如果不筛选支持相关功能的项目,查询可能会失败。

以下代码返回工作区中所有 .NET Core Web 项目的 PathGuid

IQueryResults<IProjectSnapshot> webProjects = await workspace.QueryProjectsAsync(
    project => project.Where(p => p.Capabilities.Contains("DotNetCoreWeb"))
    .With(p => new { p.Path, p.Guid }),
    cancellationToken);

具有内置筛选的查询方法

RuleResultsByRuleName 语句

在单个项目级别,每个项目都拥有一个RulesResults属性,其中包括RuleNameItems。 API 调用 RuleResultsByRuleName 可用于按规则名称进行筛选。

在以下查询中,我们并不是检索 ActiveConfigurations 中的每一个规则,而是专门针对 CompilerCommandLineArgs 规则进行处理。 查询结果将包括规则名称和与之关联的项。

IQueryResults<IProjectSnapshot> results = await workspace.QueryProjectsAsync(
    project => project.With(p => p.ActiveConfigurations
        .With(c => c.RuleResultsByRuleName("CompilerCommandLineArgs")
            .With(r => r.RuleName)
            .With(r => r.Items
                .With(i => i.Name)))),
    cancellationToken);

ProjectsByCapabilities 语句

您还可以使用查询方法,例如 ProjectsByCapabilities,其中内置了查询过滤功能。

string capabilities = "DotNetCoreWeb | DotNetCoreRazor";

// Execute the query
IQueryResults<IProjectSnapshot> webProjects = await workspace.QueryProjectsByCapabilitiesAsync(
    project => project.With(p => new { p.Path, p.Guid }),
        capabilities,
    cancellationToken);

使用嵌套查询指定所需的属性

某些参数本身是集合,可以使用嵌套查询对这些子集合执行类似的规范和筛选。

示例:

在以下示例中,嵌套查询允许筛选并指定要包含在外部查询返回的每个项目中的文件集合。

下面的查询为具有 ApplicationIcon 属性的项目生成 IProjectSnapshot。 它专门搜索这些项目中的.ico文件,目的是确定其路径以及它们是否隐藏。


IQueryResults<IProjectSnapshot> projects = await workspace.QueryProjectsByCapabilitiesAsync(
    project => project.With(project => new { project.Path, project.IsProjectFileSearchable })
    .With(project => project.PropertiesByName("ApplicationIcon")) // Retrieve a single property, if it exists
    .With(p => p.Files // Without any condition, retrieve all files in the project, but filter them
        .Where(f => f.Extension == ".ico")
        .With(f => new { f.Path, f.IsHidden })),
    "CPS",
    cancellationToken);

foreach (IProjectSnapshot project in projects)
{
    IPropertySnapshot property = project.Properties.FirstOrDefault();
    string? applicationIcon = (string?)property?.Value;

    foreach (var iconFile in project.Files)
    {
        string filePath = iconFile.Path;
        bool isHidden = iconFile.IsHidden;
    }
}

使用 Get 方法检索子集合

Visual Studio 中的项目模型包括项目的集合及其子集合,这些集合可以包含文件或项目功能等。 若要访问特定的子集合,请使用Get子句。 此子句类似于其他类型的查询,允许合并其他子句,如 With 子句,这有助于优化或约束查询结果。

在以下查询中,方法 Get 从由其特定 Guid 标识的项目中检索出子集合 Files

IQueryResults<IFileSnapshot> files = await workspace.QueryProjectsAsync(
    project => project.Where(p => p.Guid == knownGuid)
                        .Get(p => p.Files
                        .With(f => new { f.Path, f.IsHidden, f.IsSearchable })),
    cancellationToken);


foreach (IFileSnapshot file in files)
{
    string filePath = file.Path;
}

从以前返回的项查询其他信息

可以将上一个查询的结果用作其他查询的基础。

    IQueryResults<IProjectSnapshot> allProjects = await workspace.QueryProjectsAsync(
        project => project.With(p => p.Path)
        .With(p => p.Guid),
        cancellationToken);

foreach (IProjectSnapshot project in allProjects)
{
    IAsyncEnumerable<IQueryResultItem<IFileSnapshot>> files = project.Files
        .With(f => new { f.Path, f.ItemType }).QueryAsync(cancellationToken);
}

修改项目

查询结果通常不可变。 使用查询 API 的 UpdateProjectsAsync 子句访问查询结果的可变版本,这样就可以对项目和项目项进行更改。

将文件添加到查询结果中的项目的示例

IQueryResult<IProjectSnapshot> updatedProjects = await workspace.UpdateProjectsAsync(
project => project.Where(project => project.Guid == knownGuid),
project => project.CreateFile("CreatedFile.txt"),
cancellationToken);

重命名项目的示例

IQueryResult<IProjectSnapshot> updatedProjects = await workspace.UpdateProjectsAsync(
        project => project.Where(project => project.Guid == knownGuid),
        project => project.RenameFile(filePath, newFileName),
        cancellationToken);

查询项目属性

此查询以异步方式从项目集合中检索具有指定属性 RootNamespace 的项目 AssemblyVersion

// We assume that we can find the "RootNamespace" property in the result.
// However it isn't true from query API point of view.
// The query tries to retrieve items based on the condition, and if there is no such item, it will run successfully, only without returning items.
IQueryResults<IProjectSnapshot> properties = await workspace.QueryProjectsAsync(
    project => project.With(project => project.PropertiesByName("RootNamespace", "AssemblyVersion")),
    cancellationToken);

查询解决方案

除了如前所示使用项目外,还可以使用类似的技术来处理解决方案。

IQueryResults<ISolutionSnapshot> solutions = await workspace.QuerySolutionAsync(
    solution => solution.With(s => new { s.Path, s.Guid, s.ActiveConfiguration, s.ActivePlatform }),
    cancellationToken);

查询解决方案文件夹

同样,可以使用Get子句来查询解决方案文件夹。 通过此属性 IsNested ,你可以从结果中包括或排除嵌套文件夹。 解决方案资源管理器可以包含嵌套文件夹,例如用于配置设置或资源。

IQueryResults<ISolutionFolderSnapshot> solutionFolders = await workspace.QuerySolutionAsync(
    solution => solution.Get(s => s.SolutionFolders
    .With(folder => folder.Name)
    .With(folder => folder.IsNested)
    .With(folder => folder.VisualPath)), // it's a relative (virtual) path to represent how the folder is nested.),
    cancellationToken);

此示例获取所有递归嵌套的解决方案文件夹。 VisualPath这是解决方案资源管理器中显示的路径。

string visualPath = mySolutionFolder.VisualPath;

IQueryResults<ISolutionFolderSnapshot> recursivelyNestedFolders = await workspace.QuerySolutionAsync(
    solution => solution.Get(s => s.SolutionFolders
    .Where(folder => folder.VisualPath.StartsWith(visualPath) && folder.VisualPath != visualPath)
    .With(folder => folder.Name)),
    cancellationToken);

枚举项目中的源文件及其附加信息

下面是枚举项目及其代码生成器中所有 .xaml 文件的示例:

IQueryResults<IFileSnapshot> projects = await workspace.QueryProjectsByGuidAsync(
    project => project.Get(p => p.Files)
        .Where(file => file.Extension == ".xaml")
        .With(file => file.Path)
        .With(file => file.PropertiesByName("Generator")),
    guids,
    cancellationToken);

枚举具有特定扩展名的所有文件的示例,例如所有项目中的 XML 架构文件(.xsd 文件):

IQueryResults<IFileSnapshot> schemaFiles = await workspace.QueryProjectsAsync(
    project => project
            .Get(proj => proj.FilesEndingWith(".xsd"))
            .With(file => file.Path),
    cancellationToken);

foreach (IFileSnapshot file in schemaFiles)
{
    // ...
}

查询拥有特定源文件的项目

当项目和文件夹包含有关它们拥有或包含的文件的信息时,可以使用子 WithRequired 句来查询包含特定文件的项目。

查找拥有给定文件的项目的示例

string myFilePath = "c://my/file//path";
IQueryResults<IProjectSnapshot> projects = await workspace.QueryProjectsAsync(
    project => project.WithRequired(project => project.FilesByPath(myFilePath))
    .With(project =>project.Guid),
    cancellationToken);

查找包含给定文件的解决方案文件夹的示例

IQueryResults<ISolutionFolderSnapshot> solutionFolders = await workspace.QuerySolutionAsync(
    solution => solution.Get(s => s.SolutionFolders)
        .WithRequired(folder => folder.FilesByPath(myFilePath))
        .With(folder => folder.Name)
        .With(folder => folder.Guid),
    cancellationToken);

查询项目配置及其属性

项目具有一个 ConfigurationDimension 属性,可用于查找项目配置信息。 项目配置信息与项目生成配置(例如 Debug ,和 Release) 相关。

IQueryResults<IProjectSnapshot> projects = await workspace.QueryProjectsAsync(
    project => project.With(p => new { p.Guid, p.Name })
        .With(p => p.Configurations
            .With(c => c.Name)
            .With(c => c.PropertiesByName("OutputPath"))
            .With(c => c.ConfigurationDimensions)), // ConfigurationDimension is essentially Name, Value pairs, both are default properties.,
    cancellationToken);

foreach (IProjectSnapshot project in projects)
{
    foreach (IProjectConfigurationSnapshot configuration in project.Configurations)
    {
        // ...
    }
}

查询项目间的引用

还可以查询以查找引用给定项目的项目。

查找当前项目引用的所有项目的示例

IQueryResults<IProjectSnapshot> projectReferences = await workspace.QueryProjectByGuidAsync(
    project => project.With(project => project.ProjectReferences
        .With(r => r.ProjectGuid)
        .With(r => r.ReferencedProjectId)),
    knownGuid,
    cancellationToken);

查找引用当前项目的所有项目的示例

IQueryResults<IProjectSnapshot> projectReferences = await workspace.QueryProjectsAsync(
    project => project.With(p => p.Guid)
    .WithRequired(p => p.ProjectReferences
        .Where(r => r.ProjectGuid == knownGuid)),
    cancellationToken);

查询包引用

类似地,你可以查询 NuGet 包引用。

查找当前项目引用的所有包的示例

IQueryResults<IProjectSnapshot> projectReferences = await workspace.QueryProjectByGuidAsync(
    project => project.With(project => project.ActiveConfigurations
        .With(c => c.Name)
        .With(c => c.PackageReferences
            .With(p => new { p.Name, p.Version }))),
    knownGuid,
    cancellationToken);

查找引用特定 NuGet 包的所有项目的示例

string packageName = "Newtonsoft.Json";

IQueryResults<IProjectSnapshot> projectReferences = await workspace.QueryProjectsAsync(
    project => project.With(p => p.Guid)
    .WithRequired(p => p.ActiveConfigurations
        .WithRequired(c => c.PackageReferences
            .Where(package => package.Name == packageName))),
    cancellationToken);

查询项目输出组

项目配置具有有关项目输出组的信息。

IQueryResults<IProjectSnapshot> outputGroups = await workspace.QueryProjectsAsync(
    project => project.With(p => p.ActiveConfigurations
    .With(c => c.OutputGroupsByName()
        .With(o => o.Name)
        .With(o => o.Outputs))),
    cancellationToken);

查询创业项目

该解决方案具有一组可以作为可执行文件执行的启动项目。

// A query to get the list of startup project's name and path
IQueryResults<ISolutionSnapshot> startupProjects = await workspace.QuerySolutionAsync(
    solution => solution.With(solution => solution.StartupProjects
        .With(startupproject => startupproject.Name)
        .With(startupproject => startupproject.Path)),
    cancellationToken);

设置启动项目的操作

使用项目查询 API,还可以选择执行哪些项目。 下面的示例演示如何将两个项目路径设置为启动项目。

// A query to set the startup project
var results = await workspace.UpdateSolutionAsync(
    solution => solution.With(solution => solution.StartupProjects),
    solution => solution.SetStartupProjects(projectPath1, projectPath2),
    cancellationToken);

查询解决方案配置

解决方案的配置是在配置激活时包含在构建中的项目集合。 下面的示例演示如何查询解决方案配置的名称。

var results = await workspace.QuerySolutionAsync(
    solution => solution.With(solution => solution.SolutionConfigurations
    .With(c => c.Name)),
    cancellationToken);

添加解决方案配置的示例

AddSolutionConfiguration 方法采用三个参数:

  1. 第一个参数是新解决方案配置的新名称。 在此示例中,新的解决方案配置将被命名为Foo
  2. 下一个参数是新配置应基于的配置。 下面,新的解决方案配置基于现有的解决方案配置 Debug
  3. 最后,布尔值表示是否应传播解决方案配置。
await workspace.UpdateSolutionAsync(
    solution => solution.Where(solution => solution.BaseName == "mySolution"),
    solution => solution.AddSolutionConfiguration("Foo", "Debug", false),
    cancellationToken);

删除解决方案配置的示例

DeleteSolutionConfiguration 是删除解决方案配置的 API 调用。 在下面的示例中,将删除名为Foo的解决方案配置。

await workspace.UpdateSolutionAsync(
    solution => solution.Where(solution => solution.BaseName == "mySolution"),
    solution => solution.DeleteSolutionConfiguration("Foo"),
    cancellationToken);

用于加载/卸载项目的动作查询

如果需要加载/卸载项目,则需要指定解决方案和所需项目的路径。

// Unload Project
await workspace.UpdateSolutionAsync(
    solution => solution.Where(solution => solution.BaseName == "MySolution"),
    solution => solution.UnloadProject("full\\path\\to\\project.csproj"),
    cancellationToken);

// Reload Project
await workspace.UpdateSolutionAsync(
    solution => solution.Where(solution => solution.BaseName == solutionName),
    solution => solution.ReloadProject(projectPath),
    cancellationToken);

用于构建解决方案/项目的操作查询

可以在项目或解决方案级别调用生成操作。 这些生成操作包括:

  • BuildAsync
  • RebuildAsync
  • CleanAsync
  • DebugLaunchAsync
  • LaunchAsync

构建解决方案层级

在解决方案层面进行构建将构建加载到解决方案中的所有项目。 下面是生成解决方案的示例。

var result = await querySpace.Solutions
        .BuildAsync(cancellationToken);

在项目级别构建

在项目层级进行构建时,确定您要构建的项目。 在下面的示例中, myProject 将生成一个 IProjectSnapshot

 var result = await myProject.BuildAsync(cancellationToken);

用于执行保存解决方案和项目的动作查询

SaveAsync 可用于项目或解决方案级别。

在解决方案级别上保存

var result = await querySpace.Solutions
        .SaveAsync(cancellationToken);

在项目级别上保存

myProject 是一个用于保存的目标项目的 IProjectSnapshot

 var result = await myProject.SaveAsync(cancellationToken);

用于订阅查询更改的查询

SubscribeAsync 可用于跟踪项目或解决方案中的最新 IQueryResults 内容。

在下面的示例中, SubscribeAsync 将保持解决方案项目的最新状态。 SubscribeObserver 实例将传入以接收更改通知。

var solutions = await this.Extensibility.Workspaces().QuerySolutionAsync(
    solution => solution.With(solution => solution.FileName),
    cancellationToken);

var singleSolution = solutions.FirstOrDefault();

if (singleSolution is null)
{
    return;
}

var unsubscriber = await singleSolution
    .AsQueryable()
    .With(p => p.Projects)
    .SubscribeAsync(new SubscriberObserver(), CancellationToken.None);

SubscribeObserver它是实现 IObserver 接口并接收更改通知的组件。 对于上面的示例,它将实现 IObserver<IQueryResults<ISolutionSnapshot>>

private class SubscribeObserver : IObserver<IQueryResults<ISolutionSnapshot>>
{
    public void OnCompleted()
    {
        ...
    }
 
    public void OnError(Exception error)
    {
        ...
    }
 
    public void OnNext(IQueryResults<ISolutionSnapshot> value)
    {
        ...
    }
 
    public override int GetHashCode()
    {
        ...
    }
}

用于跟踪查询更改的查询

TrackUpdatesAsync 可用于跟踪项目或解决方案中的更改。

在下面的示例中, TrackUpdatesAsync 将跟踪项目中文件的更改。 TrackerObserver实例被传入以接收更改通知。

var projects = workspace.QueryProjectsAsync(
    project => project.With(project => project.Name),
    cancellationToken);

var singleProject = projects.FirstOrDefault();

if (singleProject is null)
{
    return;
}

var unsubscriber = await singleProject
    .Files
    .With(f => f.FileName)
    .TrackUpdatesAsync(new TrackerObserver(), CancellationToken.None);

TrackerObserver它是实现 IObserver 接口并接收更改通知的组件。 对于上面的示例,它将实现 IObserver<IQueryTrackUpdates<IFileSnapshot>>

private class TrackerObserver : IObserver<IQueryTrackUpdates<IFileSnapshot>>
{
    public void OnCompleted()
    {
        ...
    }
 
    public void OnError(Exception error)
    {
        ...
    }
 
    public void OnNext(IQueryTrackUpdates<IFileSnapshot> value)
    {
        ...
    }
 
    public override int GetHashCode()
    {
        ...
    }
}

要跳过的操作查询

Skip 可用于跳过查询中的 N 结果。

在此代码示例中,将跳过查询的第一个结果。 例如,如果解决方案中有三个项目,将跳过第一个结果,查询将返回其余两个项目。 请注意,顺序不保证。

IQueryResults<IProjectSnapshot> projects = await workspace.QueryProjectsAsync(
            project => project.With(p => p.Name)
            .Skip(1),
            cancellationToken);

后续步骤

若要查看有关项目查询 API 的关键字和概念,请参阅 项目查询概念

VSProjectQueryAPISample 中查看使用 Project Query API 的扩展的代码。