Wykonywanie zapytań względem interfejsu API projektu

Interfejs API zapytań projektu VisualStudio.Extensibility umożliwia wykonywanie zapytań dotyczących informacji z systemu projektu. Systemy projektów są częścią składników programu Visual Studio, które ułatwiają użytkownikom pracę z projektami i konserwowanie ich, uruchamianie kompilacji w celu generowania wyników i testowania danych wyjściowych.

Celem interfejsu API zapytań projektu jest:

  1. Praca z systemami projektów
  2. Pobieranie danych z projektów
  3. Wprowadzanie zmian w projektach

Niektóre przykłady obejmują opis plików zawartych w projekcie, pakiety NuGet, do których odwołuje się projekt, dodawanie nowych plików do projektu lub zmienianie właściwości projektu.

Więcej informacji na temat systemów projektów można znaleźć tutaj. Znajdź dokumentację koncepcyjną dotyczącą tego, czym jest system projektu, użycie i jego różne terminy tutaj.

Praca z interfejsem API zapytań projektu

To omówienie obejmuje najważniejsze scenariusze pracy z interfejsem API zapytań projektu:

Uzyskiwanie dostępu do obszaru zapytania projektu

Przed wykonaniem zapytania względem systemu projektu należy uzyskać wystąpienie obiektu obszaru zapytań projektu, które ma kilka metod asynchronicznych, które wysyłają zapytania lub aktualizują system projektu. Termin obszar zapytania projektu i termin obszar roboczy oznaczają to samo, obiekt, który zapewnia dostęp do wszystkich danych projektu.

Dostęp do obszaru zapytań projektu w rozszerzeniu poza procesem

Jeśli tworzysz rozszerzenie poza procesem, użyj następującego kodu:

WorkspacesExtensibility workSpace = this.Extensibility.Workspaces();

Dostęp do obszaru zapytań projektu w rozszerzeniu procesu

Jeśli tworzysz rozszerzenie w procesie, zamiast tego uzyskujesz dostęp do przestrzeni zapytań projektu, jak pokazano w poniższym przykładzie kodu. Jeśli nie utworzono specjalnie rozszerzenia w procesie, użyj fragmentu kodu w poprzedniej sekcji, aby uzyskać wystąpienie obiektu obszaru zapytania projektu.

W poniższym fragcie package kodu reprezentuje wystąpienie pakietu AsyncPackage, klasę używaną w tworzeniu rozszerzeń programu Visual Studio. Metoda GetServiceAsync jest stosowana do asynchronicznego pozyskiwania usługi zapytań z kontenera usługi programu Visual Studio.

IProjectSystemQueryService queryService = await package.GetServiceAsync<IProjectSystemQueryService, IProjectSystemQueryService>();
ProjectQueryableSpace workSpace = queryService.QueryableSpace;

Wykonywanie zapytań dotyczących systemu projektu dla projektu

Obiekt WorkspacesExtensibility umożliwia wykonywanie zapytań dotyczących pojedynczego projektu, jeśli masz identyfikator GUID projektu. Zazwyczaj istnieją dwa identyfikatory GUID skojarzone z projektem, jeden reprezentujący typ projektu, a drugi, który jednoznacznie reprezentuje projekt. Unikatowy identyfikator GUID projektu można znaleźć w pliku rozwiązania lub w rozszerzeniu, które można wykonać w zapytaniu o Guid właściwość, jak pokazano w następnej sekcji.

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projectList = workspace
    .ProjectsByProjectGuid(knownGuid) 
    .QueryAsync(cancellationToken);

Określanie parametrów projektu, które mają zostać uwzględnione w wyniku zapytania

Podczas wykonywania zapytań dotyczących systemu projektu można użyć With klauzul w celu kontrolowania parametrów lub metadanych uwzględnionych w wynikach zapytania. Istnieje kilka prawidłowych sposobów określania, które parametry powinny być uwzględnione.

Przykład użycia oddzielnej With klauzuli dla każdego parametru

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> allProjects = workSpace
    .Projects
    .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)
    .QueryAsync(cancellationToken);

    await foreach (IQueryResultItem<IProjectSnapshot> project in allProjects)
    {
        var projectGuid = project.Value.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.Value.PropertiesAvailableStatus.Capabilities;
    }

Przykład użycia pojedynczej With klauzuli w celu określenia wielu parametrów

Można również określić wiele żądanych parametrów w jednej With klauzuli.

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> allProjects = workSpace
    .Projects
    .With(p => new { p.Path, p.Guid, p.Capabilities })
    .QueryAsync(cancellationToken);

Przykład użycia klauzuli WithRequired

W przypadku korzystania z programu WithRequiredzwracane są tylko projekty z wymaganymi właściwościami.

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projectWithFiles = workSpace
    .Projects
    .With(p => new { p.Path, p.Guid })
    .WithRequired(p => p.Files.Where(f => f.FileName == "information.txt"))
    .QueryAsync(cancellationToken);

Przykład, gdy nie określono żadnych właściwości

Jeśli nie określono żadnych właściwości, zwracany jest domyślny zestaw właściwości.

IAsyncEnumerable<IQueryResultItem<IPropertySnapshot>> properties = myproject
    .PropertiesByName("RootNamespace", "AssemblyVersion")
    .QueryAsync(cancellationToken);

Filtrowanie wyniku zapytania

Aby ograniczyć wyniki zapytania, istnieją dwa sposoby stosowania filtrowania warunkowego: Where instrukcje i metody zapytań z wbudowanym filtrowaniem.

Przykład użycia klauzuli Where

Różne typy projektów obsługują różne zestawy możliwości. Za pomocą klauzuli Where można filtrować projekty, które obsługują określone możliwości. Zapytania mogą zakończyć się niepowodzeniem, jeśli nie filtrujesz projektów, które obsługują odpowiednie możliwości.

Poniższy kod zwraca wartości Path i Guid wszystkich projektów internetowych platformy .NET Core w obszarze roboczym:

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> webProjects = workspace
    .Projects
    .Where(p => p.Capabilities.Contains("DotNetCoreWeb"))
    .With(p => new { p.Path, p.Guid })
    .QueryAsync(cancellationToken);

Przykład użycia wbudowanego filtrowania

Możesz również użyć metod zapytań, takich jak ProjectsByCapabilities filtrowanie wbudowane w zapytanie.

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> webProjects = workspace
        .ProjectsByCapabilities("DotNetCoreWeb | DotNetCoreRazor")
        .With(p => new { p.Path, p.Guid })
        .QueryAsync(cancellationToken);

Użyj zagnieżdżonych zapytań, aby określić żądane właściwości

Niektóre parametry są kolekcjami i można użyć zapytań zagnieżdżonych do podobnej specyfikacji i filtrowania dla tych kolekcji podrzędnych.

Przykład

W poniższym przykładzie zagnieżdżone zapytanie umożliwia filtrowanie i określanie kolekcji plików, które mają zostać dołączone do każdego projektu zwróconego przez zapytanie zewnętrzne.

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projects = workspace
    .ProjectsByCapabilities("CPS")
    .With(p => new { p.Path, p.IsProjectFileSearchable })
    .With(p => p.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 }))
    .QueryAsync(cancellationToken);

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

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

Pobieranie kolekcji podrzędnej przy użyciu metody Get

Model projektu programu Visual Studio zawiera kolekcje projektów i kolekcji podrzędnych, takich jak pliki lub możliwości projektu w projektach. Aby pobrać samą kolekcję podrzędną, możesz użyć klauzuli Get . Podobnie jak w przypadku innych typów zapytań, klauzula Get umożliwia używanie innych klauzul, takich jak klauzula With do kształtowania lub ograniczania wyników.

IAsyncEnumerable<IQueryResultItem<IFileSnapshot>> files = workspace
    .Projects
    .Where(p => p.Guid == knownGuid)
    .Get(p => p.Files
        .With(f => new { f.Path, f.IsHidden, f.IsSearchable }))
    .QueryAsync(cancellationToken);

    await foreach (var file in files)
    {
        string filePath = file.Value.Path;
    }

Wykonywanie zapytań o dodatkowe informacje z wcześniej zwróconego elementu

Możesz użyć wyników z poprzedniego zapytania jako podstawy dla dodatkowych zapytań.

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> allProjects = workSpace
    .Projects
    .With(p => p.Path)
    .With(p => p.Guid)
    .QueryAsync(cancellationToken);

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

Modyfikowanie projektu

Wyniki zapytania są zwykle niezmienne. Interfejs API zapytań umożliwia również wprowadzanie zmian przy użyciu klauzuli w AsUpdatable celu uzyskania dostępu do modyfikowalnych wersji wyników zapytania, dzięki czemu można wprowadzać zmiany w projektach i elementach projektu.

Przykład dodawania pliku do projektu w wyniku zapytania

IQueryResult<IProjectSnapshot> updatedProjects = workSpace
    .ProjectsByProjectGuid(knownGuid)
    .AsUpdatable()
    .CreateFile("AdditionalInformation.txt", textContent)
    .ExecuteAsync(cancellationToken);

Przykład dodawania pliku do wcześniej zwróconego projektu

IQueryResult<IProjectSnapshot> updatedProjects = myproject
    .AsUpdatable()
    .CreateFile("AdditionalInformation2.txt", textContent)
    .ExecuteAsync(cancellationToken);

Zapytanie o właściwości projektu

Klauzulę Get można użyć do wykonywania zapytań dotyczących właściwości projektu. Poniższe zapytanie zwraca kolekcję IPropertySnapshot zawierającą wpisy dla dwóch żądanych właściwości. IPropertySnapshot zawiera nazwę właściwości, nazwę wyświetlaną i wartość w danym momencie.

// 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.
IAsyncEnumerable<IQueryResultItem<IPropertySnapshot>> properties = myProject
    .AsQueryable()  
    .Get(p => p.PropertiesByName("RootNamespace", "AssemblyVersion"))
    .QueryAsync(cancellationToken);

Wykonywanie zapytań o rozwiązania

Oprócz pracy z projektami, jak pokazano wcześniej, można użyć podobnych technik do pracy z rozwiązaniami.

IAsyncEnumerable<IQueryResultItem<ISolutionSnapshot>> solutions = workSpace
    .Solutions
    .With(s => new { s.Path, s.Guid, s.ActiveConfiguration, s.ActivePlatform })
    .QueryAsync(cancellationToken);

Wykonywanie zapytań dotyczących folderów rozwiązań

Podobnie można użyć klauzuli Get do wykonywania zapytań dotyczących folderów rozwiązań. Właściwość IsNested umożliwia dołączanie lub wykluczanie folderów zagnieżdżonych z wyników. Eksplorator rozwiązań mogą mieć zagnieżdżone foldery, takie jak ustawienie konfiguracji lub zasoby.

IAsyncEnumerable<IQueryResultItem<ISolutionFolderSnapshot>> solutionFolders = workSpace
    .Solutions
    .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.
    .QueryAsync(cancellationToken);

W tym miejscu uzyskujemy wszystkie zagnieżdżone foldery rozwiązań, projekty, pliki wewnątrz folderu rozwiązania (nie są rekursywnie zagnieżdżone):

IAsyncEnumerable<IQueryResultItem<ISolutionSnapshot>> solutionFoldersWithExtraInformation = mySolutionFolder
    .AsQueryable()
    .With(folder => folder.Files
        .With(f => f.Path))
    .With(folder => folder.Projects
        .With(p => new { p.Name, p.Guid }))
    .With(folder => folder.SolutionFolders
        .With(nested => nested.Name))
    .QueryAsync(cancellationToken);

W tym miejscu otrzymujemy wszystkie cyklicznie zagnieżdżone foldery rozwiązań. Jest VisualPath to ścieżka wyświetlana w Eksplorator rozwiązań.

string visualPath = mySolutionFolder.VisualPath;
IAsyncEnumerable<IQueryResultItem<ISolutionFolderSnapshot>> recursivelyNestedFolders = await workSpace
    .Solutions
    .Get(s => s.SolutionFolders)
    .Where(f => f.VisualPath.StartsWith(visualPath) && f.VisualPath != visualPath)
    .With(f => f.Name)
    .QueryAsync(cancellationToken);

Wyliczanie plików źródłowych z dodatkowymi informacjami w projekcie

Oto przykład wyliczania wszystkich plików xaml w projekcie i jego generatorze kodu:

IAsyncEnumerable<IQueryResultItem<IFileSnapshot>> files =
workSpace.ProjectsByProjectGuid(knownGuid)
    .Get(p => p.Files)
    .Where(file => file.Extension == ".xaml")
    .With(file => file.Path)
    .With(file => file.PropertiesByName("Generator"))
    .QueryAsync(cancellationToken);

Innym przykładem jest rozpoczęcie od projektu zwróconego z poprzedniego zapytania:

IAsyncEnumerable<IQueryResultItem<IFileSnapshot>> files = myProject
    .FilesEndingWith(".xaml")     // use built-in filter instead of 'Where' condition
    .With(file => file.Path)
    .With(file => file.PropertiesByName("Generator"))
    .QueryAsync(cancellationToken);

Możesz też pobrać wszystkie pliki zawartości, które nie są skompilowane, które są wymagane w czasie wykonywania, takie jak pliki HTML i CSS.

IAsyncEnumerable<IQueryResultItem<IFileSnapshot>> files =
    myProject.FilesWithItemTypes("Content")
        .With(file => file.Path)
        .QueryAsync(cancellationToken);

Lub wyliczyć wszystkie pliki z określonym rozszerzeniem, takie jak pliki schematu XML (.xsd pliki) we wszystkich projektach:

IAsyncEnumerable<IQueryResultItem<IFileSnapshot>> schemaFiles =
workSpace.Projects
    .Get(proj => proj.FilesEndingWith(".xsd"))
    .With(file => file.Path)
    .QueryAsync(cancellationToken);

    await foreach (IQueryResultItem<IFileSnapshot> fileResult in schemaFiles)
    {
        DoSomething(fileResult.Value.Path);
    }

Wykonywanie zapytań dotyczących projektów, które są właścicielem określonego pliku źródłowego

Projekty i foldery zawierają informacje o plikach, które są ich właścicielami lub zawierają, dzięki czemu można użyć WithRequired klauzuli do wykonywania zapytań dotyczących projektów zawierających określone pliki.

Przykład znajdowania projektów, które są właścicielem danego pliku

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projects = workspace
    .Projects
    .WithRequired(proj => proj.FilesByPath(myFilePath))
    .With(proj => proj.Guid)
    .QueryAsync(cancellationToken);

Przykład znajdowania folderów rozwiązań zawierających dany plik

IAsyncEnumerable<IQueryResultItem<ISolutionFolderSnapshot>> solutionFolders = workspace
    .Solutions
    .Get(s => s.SolutionFolders)
    .WithRequired(folder => folder.FilesByPath(myFilePath))
    .With(folder => folder.Name)
    .With(folder => folder.Guid)
    .QueryAsync(cancellationToken);

Wykonywanie zapytań o konfiguracje projektu i ich właściwości

Projekty mają ConfigurationDimension właściwość , której można użyć do znajdowania informacji o konfiguracji projektu. Informacje o konfiguracji projektu odnoszą się do konfiguracji kompilacji projektu (na przykład Debug i Release).

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projects = workspace
    .Projects
    .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.
    .QueryAsync(cancellationToken);

    await foreach (IQueryResultItem<IProjectSnapshot> project in projects)
    {
        foreach (var configuration in project.Value.Configuration)
        {
            // ...
        }
    }

Wykonywanie zapytań dotyczących odwołań do projektu

Możesz również wykonać zapytanie w celu znalezienia projektów odwołujących się do danego projektu.

Przykład znajdowania wszystkich projektów, do których odwołuje się bieżący projekt

IAsyncEnumerable<IQueryResultItem<IProjectReferenceSnapshot>> projectReferences = myProject
    .ProjectReferences
    .With(r => r.ProjectGuid)
    .With(r => r.ReferencedProjectId)
    .QueryAsync(cancellationToken);

Przykład znajdowania wszystkich projektów odwołującej się do bieżącego projektu

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projects = workSpace
    .Projects
    .With(p => p.Guid)
    .WithRequired(p => p.ProjectReferences
        .Where(r => r.ProjectGuid == knownGuid))
    .QueryAsync(cancellationToken);

Zapytanie dotyczące odwołań do pakietu

Podobnie możesz wykonywać zapytania dotyczące odwołań pakietów NuGet.

Przykład znajdowania wszystkich pakietów, do których odwołuje się bieżący projekt

IAsyncEnumerable<IQueryResultItem<IProjectConfigurationSnapshot>> configurationsWithPackageReferences = myProject
    .ActiveConfigurations
    .With(c => c.Name)
    .With(c => c.PackageReferences
        .With(p => new { p.Name, p.Version }))
    .QueryAsync(cancellationToken);

Przykład znajdowania wszystkich projektów odwołującej się do określonego pakietu NuGet

string packageName = "Newtonsoft.Json";

IAsyncEnumerable<IQueryResultItem<IProjectSnapshot>> projects = workSpace
    .Projects
    .With(p => p.Guid)
    .WithRequired(p => p.ActiveConfigurations
        .WithRequired(c => c.PackageReferences
            .Where(package => package.Name == packageName)))
    .QueryAsync(cancellationToken);

Wykonywanie zapytań dotyczących grup danych wyjściowych projektu

Konfiguracje projektu zawierają informacje o grupach danych wyjściowych projektu.

// From our list of active configurations, we need to get the first one in the list
IAsyncEnumerable<IQueryResultItem<IProjectConfigurationSnapshot>> configurations = myProject
    .ActiveConfigurations
    .QueryAsync(cancellationToken);

    IProjectConfigurationSnapshot myConfiguration = null;

    await foreach (IQueryResultItem<IProjectConfigurationSnapshot> config in configurations)
    {
        myConfiguration = config.Value;
        break;
    }

    // A multi-target project may have multiple active configurations
    IAsyncEnumerable<IQueryResultItem<IOutputGroupSnapshot>> outputGroups = myConfiguration
        .OutputGroupsByName("Built", "Symbols")
        .With(g => g.Name)
        .With(g => g.Outputs)
        .QueryAsync(cancellationToken);

Następne kroki

Przejrzyj kod rozszerzenia korzystającego z interfejsu API zapytań projektu w artykule VSProjectQueryAPISample.