Lösungen mit dem Dataverse-SDK verwenden

Im Rahmen des Lebenszyklus von der Entwicklung zur Produktion möchten Sie möglicherweise eine benutzerdefinierte Automatisierung erstellen, um bestimmte Aufgaben zu erledigen. Beispielsweise möchten Sie in Ihrer DevOps-Projektpipeline möglicherweise einen benutzerdefinierten Code oder ein Skript ausführen, mit dem eine Sandbox-Umgebung erstellt, eine nicht verwaltete Lösung importiert, diese nicht verwaltete Lösung als verwaltete Lösung exportiert und schließlich die Umgebung gelöscht wird. Sie können dies und mehr tun, indem Sie die APIs verwenden, die Ihnen zur Verfügung stehen. Nachfolgend finden Sie einige Beispiele dafür, was Sie mit dem Dataverse SDK für .NET und benutzerdefiniertem Code erreichen können.

Notiz

Sie können dieselben Vorgänge auch über die Web-API ausführen. Die damit verbundenen Aktionen sind: ImportSolution, ExportSolution, CloneAsPatch und CloneAsSolution.

Erstellen, Exportieren oder Importieren einer nicht verwalteten Lösung

Im nächsten Schritt wird gezeigt, wie einige gängige Lösungsvorgänge mithilfe von C#-Code ausgeführt werden. Informationen zum vollständigen funktionierenden C#-Codebeispiel, das diese Arten von Lösungsvorgängen (und mehr) demonstriert, finden Sie unter Beispiel: Mit Lösungen arbeiten.

Erstellen eines Herausgebers

Jede Lösung erfordert einen Herausgeber, repräsentiert durch die Publisher-Entität. Ein Herausgeber erfordert Folgendes:

  • Ein Anpassungspräfix
  • Einen eindeutigen Namen
  • Einen Anzeigenamen

Notiz

Verwenden Sie für einen fehlerfreien ALM-Ansatz immer einen benutzerdefinierten Herausgeber und eine benutzerdefinierte Lösung, nicht die Standardlösung und den Standardherausgeber, um Ihre Anpassungen bereitzustellen.

Das folgende Codebeispiel definiert zunächst einen Herausgeber und prüft dann aufgrund des eindeutigen Namens, ob dieser Herausgeber bereits vorhanden ist. Falls er bereits vorhanden ist, wurde möglicherweise das Anpassungspräfix geändert, dieses Beispiel versucht also, das aktuelle Anpassungspräfix zu erfassen. Die PublisherId wird ebenfalls erfasst, so dass der Herausgeberdatensatz gelöscht werden kann. Wenn der Herausgeber nicht gefunden wird, wird ein neuer Herausgeber mit der IOrganizationService.Create-Methode erstellt.

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

Erstellen einer nicht verwalteten Lösung

Nachdem Sie einen benutzerdefinierten Herausgeber zur Verfügung haben, können Sie eine nicht verwaltete Lösung erstellen. In der folgenden Tabelle sind die Felder mit Beschreibungen aufgelistet, die in einer Lösung enthalten sind.

Feldbezeichnung Beschreibung
Anzeigename Der Name der Lösung.
Name Microsoft Dataverse generiert einen eindeutigen Namen basierend auf dem Anzeigename. Sie können den eindeutigen Namen bearbeiten. Der eindeutige Name darf nur alphanumerische Zeichen oder das Unterstrichzeichen enthalten.
Herausgeber Verwenden Sie die Herausgeber-Suche, um die Lösung dem Herausgeber zuzuordnen.
Version Geben Sie eine Version im folgenden Format an: Hauptversion.Nebenversion.Build.Revision (zum Beispiel 1.0.0.0.
Konfigurationsseite Wenn Ihre Lösung eine HTML-Webressource enthält, können Sie diese Suche verwenden, um sie als festgelegte Lösungskonfigurationsseite hinzuzufügen.
Beschreibung Verwenden Sie das Feld, um alle relevanten Details über die Lösung einzufügen.

Im Folgenden finden Sie Beispielcode zum Erstellen einer nicht verwalteten Lösung, die den im vorherigen Abschnitt erstellten Herausgeber verwendet.

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

Nach dem Erstellen einer nicht verwalteten Lösung können Sie Lösungskomponenten hinzufügen, indem Sie sie im Kontext dieser Lösung erstellen oder indem Sie vorhandene Komponenten von anderen Lösungen hinzufügen. Weitere Information: Hinzufügen einer neuen Lösungskomponente und Hinzufügen einer vorhandenen Lösungskomponente

Exportieren einer nicht verwalteten Lösung

Dieses Codenbeispiel zeigt, wie eine nicht verwaltete Lösung exportiert oder eine verwaltete Lösung gepackt wird. Der Code verwendet die Klasse ExportSolutionRequest, um eine komprimierte Datei zu exportieren, die eine nicht verwalte Lösung repräsentiert. Die Option zur Erstellung einer verwalteten Lösung wird mit der Managed-Eigenschaft festgelegt. In diesem Beispiel wird eine Datei mit dem Namen samplesolution.zip im Ausgangsordner gespeichert.

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

Importieren einer nicht verwalteten Lösung

Das Importieren (oder Aktualisieren) einer Lösung mithilfe von Code erfolgt mit ImportSolutionRequest.

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

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

_serviceProxy.Execute(impSolReq);

Nachverfolgen des Importerfolgs

Mit der ImportJob-Entität können Sie Daten zum Erfolg des Lösungsimports erfassen. Wenn Sie eine ImportJobId für die ImportSolutionRequest angegeben haben, können Sie diesen Wert verwenden, um die ImportJob-Entität nach dem Status des Imports abzufragen. Die ImportJobId kann auch für den Download einer Importprotokolldatei mit der Meldung RetrieveFormattedImportJobResultsRequest verwendet werden.

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

Der Inhalt der Eigenschaft Data eine Zeichenfolge, die eine XML-Datei der Lösung repräsentiert.

Hinzufügen und Entfernen von Lösungskomponenten

Erfahren Sie, wie Sie Lösungskomponenten mithilfe von Code hinzufügen und entfernen.

Hinzufügen einer neuen Lösungskomponente

Dieses Beispiel zeigt, wie eine Lösungskomponente erstellt wird, die mit einer bestimmten Lösung verbunden ist. Wenn Sie die Lösungskomponente bei der Erstellung nicht mit einer bestimmten Lösung verbinden, wird sie nur der Standardlösung hinzugefügt und Sie müssen sie einer Lösung manuell oder mit dem in Hinzufügen einer vorhandenen Lösungskomponente enthaltenen Code hinzufügen.

Dieser Code erstellt einen neuen globalen Optionssatz und fügt ihn der Lösung mit dem eindeutigen Namen _primarySolutionName hinzu.

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);

Hinzufügen einer vorhandenen Lösungskomponente

Dieses Beispiel zeigt, wie einer Lösungskomponente eine vorhandene Lösung hinzugefügt wird.

Der folgende Code verwendet die AddSolutionComponentRequest zum Hinzufügen der Account-Entität als Lösungskomponente zu einer nicht verwalteten Lösung.

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

Entfernen einer Lösungskomponente

Dieses Beispiel zeigt, wie eine Lösungskomponente von einer nicht verwalteten Lösung entfernt wird. Der folgende Code verwendet die RemoveSolutionComponentRequest zum Entfernen einer Entitätslösungskomponente von einer nicht verwalteten Lösung. solution.UniqueName verweist auf die Lösung, die in Erstellen einer nicht verwalteten Lösung erstellt wird.

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

Löschen einer Lösung

Das folgende Beispiel zeigt, wie Sie eine Lösung mithilfe der Lösung uniquename abrufen und dann die solutionid aus den Ergebnissen extrahieren. Das Beispiel verwendet dann die solutionid mit dem IOrganizationService. Delete-Methode zum Löschen der Lösung.

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

Klonen, Patchen und Aktualisieren

Sie können zusätzliche Lösungsvorgänge ausführen, indem Sie die verfügbaren APIs verwenden. Verwenden Sie zum Klonen und Patchen von Lösungen die Anforderungen CloneAsPatchRequest und CloneAsSolutionRequest. Informationen zum Klonen und Patchen finden Sie unter Erstellen von Lösungspatches.

Verwenden Sie zur Durchführung von Lösungsupgrades die Anforderungen StageAndUpgradeRequest und DeleteAndPromoteRequest. Weitere Informationen zum Staging- und Upgrade-Prozess finden Sie unter Aktualisieren einer Lösung.

Erkennen von Lösungsabhängigkeiten

Dieses Beispiel zeigt, wie Sie einen Bericht erstellen, der die Abhängigkeiten zwischen Lösungskomponenten zeigt.

Dieser Code leistet Folgendes:

  • Abrufen aller Komponenten für eine Lösung.

  • Abrufen aller Abhängigkeiten für jede Komponente.

  • Für jede gefundene Abhängigkeit wird ein bericht angezeigt, der die Abhängigkeit beschreibt.

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

Die DependencyReport-Methode befindet sich im folgenden Codebeispiel.

Abhängigkeitsbericht

Die Methode DependencyReport bietet eine benutzerfreundlichere Nachricht, basierend auf den Informationen, die innerhalb der Abhängigkeit gefunden werden.

Notiz

In diesem Beispiel wird die Methode nur teilweise implementiert. Sie kann Nachrichten nur für Attribut- und Optionssatzlösungskomponenten anzeigen.

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

Erkennen, ob eine Lösungskomponenten gelöscht werden kann

Verwenden Sie die RetrieveDependenciesForDeleteRequest-Meldung, um andere Lösungskomponenten zu identifizieren, die die Löschung einer bestimmten Lösungskomponente verhindern können. Das folgende Codebeispiel sucht nach Attributen, die einen bekannten globalen Optionssatz verwenden. Jedes Attribut, das den globalen Optionssatz verwendet, verhindert das Löschen des globalen Optionssatzes.

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