Praca z rozwiązaniami przy użyciu interfejsów API Dataverse SDK

W ramach opracowywania cyklu życia aplikacji można utworzyć automatyzację niestandardową w celu obsługi określonych zadań. Na przykład w potoku projektu DevOps może zaistnieć konieczność wykonania niestandardowego kodu lub skryptu tworzącego środowisko piaskownicy, zaimportowania rozwiązania niezarządzanego, wyeksportowania rozwiązania niezarządzanego jako rozwiązanie zarządzane i ostatecznie usunięcia środowiska. Można to zrobić i wykonać, korzystając z dostępnych interfejsów API. Poniżej przedstawiamy kilka przykładów tego, co możesz osiągnąć, używając Dataverse SDK dla .NET i własnego kodu.

Uwaga

Te same operacje można również wykonać przy użyciu interfejsu API sieci Web. Odpowiednie akcje to: ImportSolution, ExportSolution, CloneAsPatch i CloneAsSolution.

Tworzenie, eksportowanie lub importowanie rozwiązania niezarządzanego

Zobaczmy, jak wykonywać typowe operacje na rozwiązaniach za pomocą kodu C#. Aby wyświetlić pełną próbkę kodu w języku C#, która prezentuje te typy operacji rozwiązania (i nie tylko), zobacz przykład: Praca z rozwiązaniami.

Utwórz wydawcę

Każde rozwiązanie wymaga wydawcy, reprezentowanego przez encję wydawca. Wydawca wymaga następujących czynności:

  • Prefiks nazwy dostosowywania
  • Unikatowa nazwa
  • Przyjazna nazwa

Uwaga

W celu poprawnego ALM do wdrażania dostosowań zawsze używaj niestandardowego wydawcy i rozwiązania, a nie rozwiązania domyślnego i wydawcy.

Poniższa próbka kodu definiuje w pierwszej kolejności wydawcę, a następnie sprawdza, czy wydawca już istnieje na podstawie unikatowej nazwy. Jeśli już istnieje, może to oznaczać, że prefiks dostosowań się zmienił, więc ten przykład próbuje uchwycić bieżący prefiksu dostosowywania. Element PublisherId jest również uchwycony w taki sposób, aby można było usunąć rekord wydawcy. Jeśli wydawca nie zostanie odnaleziony, nowy wydawca zostanie utworzony przy użyciu metody IOrganizationService. Metody Utwórz.

// Define a new publisher
Publisher _myPublisher = new Publisher
{
   UniqueName = "contoso-publisher",
   FriendlyName = "Contoso publisher",
   SupportingWebsiteUrl =
      "https://learn.microsoft.com/powerapps/developer/data-platform/overview",
   CustomizationPrefix = "contoso",
   EMailAddress = "someone@contoso.com",
   Description = "This publisher was created from sample code"
};

// Does the publisher already exist?
QueryExpression querySamplePublisher = new QueryExpression
{
   EntityName = Publisher.EntityLogicalName,
   ColumnSet = new ColumnSet("publisherid", "customizationprefix"),
   Criteria = new FilterExpression()
};

querySamplePublisher.Criteria.AddCondition("uniquename", ConditionOperator.Equal,
   _myPublisher.UniqueName);

EntityCollection querySamplePublisherResults =
   _serviceProxy.RetrieveMultiple(querySamplePublisher);

Publisher SamplePublisherResults = null;

// If the publisher already exists, use it
if (querySamplePublisherResults.Entities.Count > 0)
{
   SamplePublisherResults = (Publisher)querySamplePublisherResults.Entities[0];
   _publisherId = (Guid)SamplePublisherResults.PublisherId;
   _customizationPrefix = SamplePublisherResults.CustomizationPrefix;
}

// If the publisher doesn't exist, create it
if (SamplePublisherResults == null)
{
   _publisherId = _serviceProxy.Create(_myPublisher);

   Console.WriteLine(String.Format("Created publisher: {0}.",
   _myPublisher.FriendlyName));

   _customizationPrefix = _myPublisher.CustomizationPrefix;
}

Tworzenie rozwiązania niezarządzanego

Po udostępnieniu niestandardowego wydawcy można utworzyć rozwiązanie niezarządzane. W poniższej tabeli zamieszczono listę pól zawierających opisy zawarte w rozwiązaniu.

Etykieta pola Opis
Wyświetlana nazwa Nazwa rozwiązania.
Nazwisko Microsoft Dataverse generuje unikatową nazwę na podstawie Wyświetlanej nazwy. Unikatową nazwę można edytować. Nazwa może zawierać tylko znaki alfanumeryczne i znak podkreślnik.
Wydawca Użyj funkcji wyszukiwanie Wydawcy,aby skojarzyć rozwiązanie z wydawcą.
Wersja Określ wersję przy użyciu następującego formatu: główna.pomocnicza.kompilacja.poprawka (na przykład 1.0.0.0.
Strona konfiguracji Jeśli w rozwiązaniu dołączono zasób sieci Web w formacie HTML, można użyć tego wyszukiwania, aby dodać je jako wyznaczoną stronę konfiguracyjną rozwiązania.
Opis Użyj tego pola, aby podać wszystkie istotne informacje o rozwiązaniu.

Poniżej przedstawiono przykładowy kod do utworzenia rozwiązania niezarządzanego, w którym jest używany wydawca utworzony w poprzedniej sekcji.

// Create a solution
Solution solution = new Solution
{
   UniqueName = "sample-solution",
   FriendlyName = "Sample solution",
   PublisherId = new EntityReference(Publisher.EntityLogicalName, _publisherId),
   Description = "This solution was created by sample code.",
   Version = "1.0"
};

// Check whether the solution already exists
QueryExpression queryCheckForSampleSolution = new QueryExpression
{
   EntityName = Solution.EntityLogicalName,
   ColumnSet = new ColumnSet(),
   Criteria = new FilterExpression()
};

queryCheckForSampleSolution.Criteria.AddCondition("uniquename",
   ConditionOperator.Equal, solution.UniqueName);

// Attempt to retrieve the solution
EntityCollection querySampleSolutionResults =
   _serviceProxy.RetrieveMultiple(queryCheckForSampleSolution);

// Create the solution if it doesn't already exist
Solution SampleSolutionResults = null;

if (querySampleSolutionResults.Entities.Count > 0)
{
   SampleSolutionResults = (Solution)querySampleSolutionResults.Entities[0];
   _solutionsSampleSolutionId = (Guid)SampleSolutionResults.SolutionId;
}

if (SampleSolutionResults == null)
{
   _solutionsSampleSolutionId = _serviceProxy.Create(solution);
}

Po utworzeniu rozwiązania niezarządzanego można dodać składniki rozwiązania, tworząc je w kontekście tego rozwiązania lub dodając istniejące składniki z innych rozwiązań. Więcej informacji: Dodawanie nowego składnika rozwiązania i Dodawanie istniejącego składnika rozwiązania

Eksportowanie rozwiązania niezarządzanego

Ten przykładowy kod ilustruje sposób eksportowania rozwiązania niezarządzanego lub pakowania rozwiązania zarządzanego. W kodzie jest używana klasa ExportSolutionRequest w celu wyeksportowania skompresowanego pliku reprezentującego rozwiązanie niezarządzane. Opcja tworzenia rozwiązania zarządzanego jest ustawiana przy użyciu właściwości zarządzanej. W tym przykładzie zapisywany jest plik o nazwie samplesolution.zip w folderze wyjściowym.

// Export a solution
ExportSolutionRequest exportSolutionRequest = new ExportSolutionRequest();
exportSolutionRequest.Managed = false;
exportSolutionRequest.SolutionName = solution.UniqueName;

ExportSolutionResponse exportSolutionResponse =
   (ExportSolutionResponse)_serviceProxy.Execute(exportSolutionRequest);

byte[] exportXml = exportSolutionResponse.ExportSolutionFile;
string filename = solution.UniqueName + ".zip";

File.WriteAllBytes(outputDir + filename, exportXml);

Console.WriteLine("Solution exported to {0}.", outputDir + filename);

Importowanie rozwiązania niezarządzanego

Importowanie (lub uaktualnianie) rozwiązania przy użyciu kodu jest realizowane za pomocą ImportSolutionRequest.

// Install or upgrade a solution
byte[] fileBytes = File.ReadAllBytes(ManagedSolutionLocation);

ImportSolutionRequest impSolReq = new ImportSolutionRequest()
{
   CustomizationFile = fileBytes
};

_serviceProxy.Execute(impSolReq);

Śledzenie sukcesu importu

Encja ImportJob może służyć do przechwytywania danych o powodzeniu importu rozwiązania. Jeśli ImportJobId zostanie określona dla ImportSolutionRequest, można jej użyć w celu wykonania zapytania do encji ImportJob dotyczącej stanu importu. ImportJobId można również użyć do pobrania pliku importu dziennika za pomocą RetrieveFormattedImportJobResultsRequest.

// Monitor solution import success
byte[] fileBytesWithMonitoring = File.ReadAllBytes(ManagedSolutionLocation);

ImportSolutionRequest impSolReqWithMonitoring = new ImportSolutionRequest()
{
   CustomizationFile = fileBytes,
   ImportJobId = Guid.NewGuid()
};

_serviceProxy.Execute(impSolReqWithMonitoring);

ImportJob job = (ImportJob)_serviceProxy.Retrieve(ImportJob.EntityLogicalName,
   impSolReqWithMonitoring.ImportJobId, new ColumnSet(new System.String[] { "data",
   "solutionname" }));

System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.LoadXml(job.Data);

String ImportedSolutionName =
   doc.SelectSingleNode("//solutionManifest/UniqueName").InnerText;

String SolutionImportResult =
   doc.SelectSingleNode("//solutionManifest/result/\@result").Value;

Console.WriteLine("Report from the ImportJob data");

Console.WriteLine("Solution Unique name: {0}", ImportedSolutionName);

Console.WriteLine("Solution Import Result: {0}", SolutionImportResult);

Console.WriteLine("");

// This code displays the results for Global Option sets installed as part of a
// solution.

System.Xml.XmlNodeList optionSets = doc.SelectNodes("//optionSets/optionSet");

foreach (System.Xml.XmlNode node in optionSets)
{
   string OptionSetName = node.Attributes["LocalizedName"].Value;
   string result = node.FirstChild.Attributes["result"].Value;

   if (result == "success")
   {
      Console.WriteLine("{0} result: {1}",OptionSetName, result);
   }
   else
   {
      string errorCode = node.FirstChild.Attributes["errorcode"].Value;
      string errorText = node.FirstChild.Attributes["errortext"].Value;

      Console.WriteLine("{0} result: {1} Code: {2} Description: {3}",OptionSetName,
      result, errorCode, errorText);
   }
}

Zawartość właściwości Data jest ciągiem reprezentującym plik XML rozwiązania.

Dodawanie i usuwanie składników rozwiązania

Informacje o dodawaniu i usuwaniu składników rozwiązania przy użyciu kodu.

Dodawanie nowego składnika do rozwiązania

W tym przykładzie pokazano, jak utworzyć składnik rozwiązania skojarzony z określonym rozwiązaniem. Jeśli składnik rozwiązania nie zostanie skojarzony z określonym rozwiązaniem podczas jego tworzenia, zostanie dodany do rozwiązania domyślnego i będzie konieczne ręczne dodanie go do rozwiązania lub użycie kodu zawartego w polu Dodaj istniejący składnik rozwiązania.

Ten kod powoduje utworzenie nowego globalnego zestaw opcji i dodanie go do rozwiązania o unikatowej nazwie równej _primarySolutionName.

OptionSetMetadata optionSetMetadata = new OptionSetMetadata()
{
   Name = _globalOptionSetName,
   DisplayName = new Label("Example Option Set", _languageCode),
   IsGlobal = true,
   OptionSetType = OptionSetType.Picklist,
   Options =
{
   new OptionMetadata(new Label("Option 1", _languageCode), 1),
   new OptionMetadata(new Label("Option 2", _languageCode), 2)
}
};
CreateOptionSetRequest createOptionSetRequest = new CreateOptionSetRequest
{
   OptionSet = optionSetMetadata                
};

createOptionSetRequest.SolutionUniqueName = _primarySolutionName;
_serviceProxy.Execute(createOptionSetRequest);

Dodaj istniejący składnik rozwiązania

W tym przykładzie pokazano, jak dodać istniejący składnik rozwiązania do rozwiązania.

W poniższym kodzie zastosowano AddSolutionComponentRequest aby dodać encję Account jako składnik rozwiązania do rozwiązania niezarządzanego.

// Add an existing Solution Component
// Add the Account entity to the solution
RetrieveEntityRequest retrieveForAddAccountRequest = new RetrieveEntityRequest()
{
   LogicalName = Account.EntityLogicalName
};
RetrieveEntityResponse retrieveForAddAccountResponse = (RetrieveEntityResponse)_serviceProxy.Execute(retrieveForAddAccountRequest);
AddSolutionComponentRequest addReq = new AddSolutionComponentRequest()
{
   ComponentType = (int)componenttype.Entity,
   ComponentId = (Guid)retrieveForAddAccountResponse.EntityMetadata.MetadataId,
   SolutionUniqueName = solution.UniqueName
};
_serviceProxy.Execute(addReq);

Usuwanie składnika rozwiązania

W tym przykładzie pokazano, jak usunąć istniejący składnik rozwiązania niezarządzanego. W poniższym kodzie zastosowano RemoveSolutionComponentRequest aby usunąć składnik rozwiązania z rozwiązania niezarządzanego. solution.UniqueName zawiera odwołanie do rozwiązania utworzonego w ramach tworzenia rozwiązania niezarządzanego.

// Remove a Solution Component
// Remove the Account entity from the solution
RetrieveEntityRequest retrieveForRemoveAccountRequest = new RetrieveEntityRequest()
{
   LogicalName = Account.EntityLogicalName
};
RetrieveEntityResponse retrieveForRemoveAccountResponse = (RetrieveEntityResponse)_serviceProxy.Execute(retrieveForRemoveAccountRequest);

RemoveSolutionComponentRequest removeReq = new RemoveSolutionComponentRequest()
{
   ComponentId = (Guid)retrieveForRemoveAccountResponse.EntityMetadata.MetadataId,
   ComponentType = (int)componenttype.Entity,
   SolutionUniqueName = solution.UniqueName
};
_serviceProxy.Execute(removeReq);

Usuwanie rozwiązania

W poniższym przykładzie pokazano, jak pobrać rozwiązanie przy użyciu rozwiązania uniquename, a następnie wyodrębnić solutionid spośród wyników. Próbka korzysta z solutionid wraz z IOrganizationService. Metoda Delete usuwa rozwiązanie.

// Delete a solution

QueryExpression queryImportedSolution = new QueryExpression
{
    EntityName = Solution.EntityLogicalName,
    ColumnSet = new ColumnSet(new string[] { "solutionid", "friendlyname" }),
    Criteria = new FilterExpression()
};


queryImportedSolution.Criteria.AddCondition("uniquename", ConditionOperator.Equal, ImportedSolutionName);

Solution ImportedSolution = (Solution)_serviceProxy.RetrieveMultiple(queryImportedSolution).Entities[0];

_serviceProxy.Delete(Solution.EntityLogicalName, (Guid)ImportedSolution.SolutionId);

Console.WriteLine("Deleted the {0} solution.", ImportedSolution.FriendlyName);

Klonowanie, poprawianie i uaktualnianie

Korzystając z dostępnych interfejsów API, można wykonywać dodatkowe operacje na rozwiązaniach. Do klonowania i poprawiania rozwiązań są używane CloneAsPatchRequest i CloneAsSolutionRequest. Aby uzyskać informacje na temat klonowania i poprawiania, zobacz Tworzenie poprawek rozwiązań.

Podczas uaktualniania rozwiązania korzystaj z StageAndUpgradeRequest i DeleteAndPromoteRequest. Więcej informacji na temat procesu przemieszczania i uaktualniania można znaleźć w artykule uaktualnianie i aktualizowanie rozwiązania.

Wykrywanie zależności rozwiązań

W tym przykładzie przedstawiono sposób tworzenia raportu pokazującego zależności między składnikami rozwiązania.

Ten kod:

  • Pobierze wszystkie składniki rozwiązania.

  • Pobierze wszystkie zależności dla każdego składnika.

  • Dla każdej znalezionej zależności wyświetli raport z opisem zależności.

// Grab all Solution Components for a solution.
QueryByAttribute componentQuery = new QueryByAttribute
{
    EntityName = SolutionComponent.EntityLogicalName,
    ColumnSet = new ColumnSet("componenttype", "objectid", "solutioncomponentid", "solutionid"),
    Attributes = { "solutionid" },

    // In your code, this value would probably come from another query.
    Values = { _primarySolutionId }
};

IEnumerable<SolutionComponent> allComponents =
    _serviceProxy.RetrieveMultiple(componentQuery).Entities.Cast<SolutionComponent>();

foreach (SolutionComponent component in allComponents)
{
    // For each solution component, retrieve all dependencies for the component.
    RetrieveDependentComponentsRequest dependentComponentsRequest =
        new RetrieveDependentComponentsRequest
        {
            ComponentType = component.ComponentType.Value,
            ObjectId = component.ObjectId.Value
        };
    RetrieveDependentComponentsResponse dependentComponentsResponse =
        (RetrieveDependentComponentsResponse)_serviceProxy.Execute(dependentComponentsRequest);

    // If there are no dependent components, we can ignore this component.
    if (dependentComponentsResponse.EntityCollection.Entities.Any() == false)
        continue;

    // If there are dependencies upon this solution component, and the solution
    // itself is managed, then you will be unable to delete the solution.
    Console.WriteLine("Found {0} dependencies for Component {1} of type {2}",
        dependentComponentsResponse.EntityCollection.Entities.Count,
        component.ObjectId.Value,
        component.ComponentType.Value
        );
    //A more complete report requires more code
    foreach (Dependency d in dependentComponentsResponse.EntityCollection.Entities)
    {
        DependencyReport(d);
    }
}

Metoda DependencyReport znajduje się w następującym przykładzie kodu.

Raport zależności

Metoda DependencyReport dostarcza bardziej przyjazne komunikaty na podstawie informacji zawartych w zależności.

Uwaga

W tym przykładzie metoda jest tylko częściowo zaimplementowana. Umożliwia wyświetlanie komunikatów tylko dla składników rozwiązań zestawu opcji i atrybutów.

/// <summary>
/// Shows how to get a more friendly message based on information within the dependency
/// <param name="dependency">A Dependency returned from the RetrieveDependentComponents message</param>
/// </summary> 
public void DependencyReport(Dependency dependency)
{
 // These strings represent parameters for the message.
    String dependentComponentName = "";
    String dependentComponentTypeName = "";
    String dependentComponentSolutionName = "";
    String requiredComponentName = "";
    String requiredComponentTypeName = "";
    String requiredComponentSolutionName = "";

 // The ComponentType global Option Set contains options for each possible component.
    RetrieveOptionSetRequest componentTypeRequest = new RetrieveOptionSetRequest
    {
     Name = "componenttype"
    };

    RetrieveOptionSetResponse componentTypeResponse = (RetrieveOptionSetResponse)_serviceProxy.Execute(componentTypeRequest);
    OptionSetMetadata componentTypeOptionSet = (OptionSetMetadata)componentTypeResponse.OptionSetMetadata;
 // Match the Component type with the option value and get the label value of the option.
    foreach (OptionMetadata opt in componentTypeOptionSet.Options)
    {
     if (dependency.DependentComponentType.Value == opt.Value)
     {
      dependentComponentTypeName = opt.Label.UserLocalizedLabel.Label;
     }
     if (dependency.RequiredComponentType.Value == opt.Value)
     {
      requiredComponentTypeName = opt.Label.UserLocalizedLabel.Label;
     }
    }
 // The name or display name of the compoent is retrieved in different ways depending on the component type
    dependentComponentName = getComponentName(dependency.DependentComponentType.Value, (Guid)dependency.DependentComponentObjectId);
    requiredComponentName = getComponentName(dependency.RequiredComponentType.Value, (Guid)dependency.RequiredComponentObjectId);

 // Retrieve the friendly name for the dependent solution.
    Solution dependentSolution = (Solution)_serviceProxy.Retrieve
     (
      Solution.EntityLogicalName,
      (Guid)dependency.DependentComponentBaseSolutionId,
      new ColumnSet("friendlyname")
     );
    dependentComponentSolutionName = dependentSolution.FriendlyName;
    
 // Retrieve the friendly name for the required solution.
    Solution requiredSolution = (Solution)_serviceProxy.Retrieve
      (
       Solution.EntityLogicalName,
       (Guid)dependency.RequiredComponentBaseSolutionId,
       new ColumnSet("friendlyname")
      );
    requiredComponentSolutionName = requiredSolution.FriendlyName;

 // Display the message
     Console.WriteLine("The {0} {1} in the {2} depends on the {3} {4} in the {5} solution.",
     dependentComponentName,
     dependentComponentTypeName,
     dependentComponentSolutionName,
     requiredComponentName,
     requiredComponentTypeName,
     requiredComponentSolutionName);
}

Wykrywa czy składniki rozwiązania można usunąć

W celu zidentyfikowania innych rozwiązania, które uniemożliwiają usunięcie określonego składnika rozwiązania należy użyć komunikatu RetrieveDependenciesForDeleteRequest. Poniższe przykładowe kody wyszukują dowolne atrybuty przy użyciu znanego globalnego zestawu opcji. Każdy atrybut wykorzystujący globalny zestaw opcji uniemożliwi usunięcie globalnego elementu zestawu opcji.

// Use the RetrieveOptionSetRequest message to retrieve  
// a global option set by it's name.
RetrieveOptionSetRequest retrieveOptionSetRequest =
    new RetrieveOptionSetRequest
    {
     Name = _globalOptionSetName
    };

// Execute the request.
RetrieveOptionSetResponse retrieveOptionSetResponse =
    (RetrieveOptionSetResponse)_serviceProxy.Execute(
    retrieveOptionSetRequest);
_globalOptionSetId = retrieveOptionSetResponse.OptionSetMetadata.MetadataId;
if (_globalOptionSetId != null)
{ 
 // Use the global OptionSet MetadataId with the appropriate componenttype
 // to call RetrieveDependenciesForDeleteRequest
 RetrieveDependenciesForDeleteRequest retrieveDependenciesForDeleteRequest = new RetrieveDependenciesForDeleteRequest 
{ 
 ComponentType = (int)componenttype.OptionSet,
 ObjectId = (Guid)_globalOptionSetId
};

 RetrieveDependenciesForDeleteResponse retrieveDependenciesForDeleteResponse =
  (RetrieveDependenciesForDeleteResponse)_serviceProxy.Execute(retrieveDependenciesForDeleteRequest);
 Console.WriteLine("");
 foreach (Dependency d in retrieveDependenciesForDeleteResponse.EntityCollection.Entities)
 {

  if (d.DependentComponentType.Value == 2)//Just testing for Attributes
  {
   String attributeLabel = "";
   RetrieveAttributeRequest retrieveAttributeRequest = new RetrieveAttributeRequest
   {
    MetadataId = (Guid)d.DependentComponentObjectId
   };
   RetrieveAttributeResponse retrieveAttributeResponse = (RetrieveAttributeResponse)_serviceProxy.Execute(retrieveAttributeRequest);

   AttributeMetadata attmet = retrieveAttributeResponse.AttributeMetadata;

   attributeLabel = attmet.DisplayName.UserLocalizedLabel.Label;
  
    Console.WriteLine("An {0} named {1} will prevent deleting the {2} global option set.", 
   (componenttype)d.DependentComponentType.Value, 
   attributeLabel, 
   _globalOptionSetName);
  }
 }
}