Werken met oplossingen via de Dataverse SDK

In het kader van de levenscyclus van ontwikkeling tot productie wilt u misschien aangepaste automatisering maken om bepaalde taken uit te voeren. In uw DevOps-projectpijplijn wilt u misschien wat aangepaste code of script uitvoeren die een sandbox-omgeving maakt, een onbeheerde oplossing importeert, die onbeheerde oplossing exporteert als beheerde oplossing en ten slotte de omgeving verwijdert. U kunt dit en meer doen door de API's te gebruiken die voor u beschikbaar zijn. Hieronder vindt u enkele voorbeelden van wat u kunt bereiken met de Dataverse SDK voor .NET en aangepaste code.

Notitie

U kunt dezelfde bewerkingen ook uitvoeren met de web-API. De desbetreffende acties zijn: ImportSolution, ExportSolution, CloneAsPatch en CloneAsSolution.

Maak, exporteer of importeer een onbeheerde oplossing

Laten we eens kijken hoe we enkele veelvoorkomende oplossingsbewerkingen kunnen uitvoeren met behulp van C#-code. Zie Voorbeeld: werken met oplossingen voor het volledige, werkende C#-codevoorbeeld dat dit soort oplossingsbewerkingen (en meer) laat zien.

Een uitgever maken

Elke oplossing vereist een uitgever, die door de entiteit Uitgever wordt weergegeven. Een uitgever vereist het volgende:

  • Een aanpassingsvoorvoegsel
  • Een unieke naam
  • Een beschrijvende naam

Notitie

Gebruik voor een goede ALM-aanpak altijd een aangepaste uitgever en oplossing, niet de standaardoplossing en -uitgever, voor het implementeren van uw aanpassingen.

Het volgende codevoorbeeld definieert eerst een uitgever en controleert vervolgens of de uitgever al bestaat op basis van de unieke naam. Als deze al bestaat, kan het aanpassingsvoorvoegsel zijn gewijzigd, dus probeert dit voorbeeld het huidige aanpassingsvoorvoegsel vast te leggen. PublisherId is ook vastgelegd zodat de uitgeverrecord kan worden verwijderd. Als de uitgever niet wordt gevonden, wordt een nieuwe uitgever gemaakt met de methode IOrganizationService..Create.

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

Een onbeheerde oplossing maken

Wanneer een aangepaste uitgever beschikbaar is, kunt u vervolgens een onbeheerde oplossing maken. De volgende tabel toont de velden met beschrijvingen van een oplossing.

Veldlabel Beschrijving
Weergavenaam De naam van de oplossing.
Naam Microsoft Dataverse maakt een unieke naam op basis van de Weergavenaam. U kunt de unieke naam bewerken. De unieke naam mag alleen alfanumerieke tekens en het onderstrepingsteken bevatten.
Uitgever Gebruik de zoekfunctie van Uitgever om de oplossing te koppelen aan een uitgever.
Versie Geef de versie op in de volgende indeling: primair.secundair.build.revisie, bijvoorbeeld: 1.0.0.0.
Configuratiepagina Als u een HTML-webresource in de oplossing opneemt, kunt u deze zoekactie gebruiken om deze als speciale configuratiepagina van de oplossing toe te voegen.
Beschrijving Gebruik dit veld om een relevante gegevens over uw oplossing op te nemen.

Hieronder vindt u voorbeeldcode om een onbeheerde oplossing te maken die gebruikmaakt van de uitgever die we in de vorige sectie hebben gemaakt.

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

Als u een onbeheerde oplossing maakt, kunt u oplossingsonderdelen toevoegen door ze in de context van deze oplossing te maken of door bestaande onderdelen van andere oplossingen toe te voegen. Meer informatie: Een nieuw oplossingsonderdeel toevoegen en Een bestaand oplossingsonderdeel toevoegen

Een onbeheerde oplossing importeren

Dit codevoorbeeld toont hoe u een onbeheerde oplossing exporteert of een beheerde oplossing verpakt. De code gebruikt de klasse ExportSolutionRequest om een gecomprimeerd bestand te exporteren dat staat voor een onbeheerde oplossing. De optie om een beheerde oplossing te maken, wordt ingesteld met de eigenschap Beheerd. In dit voorbeeld wordt een bestand met de naam samplesolution.zip opgeslagen in de uitvoermap.

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

Een onbeheerde oplossing importeren

Het importeren (of upgraden) van een oplossing met behulp van code wordt uitgevoerd met ImportSolutionRequest.

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

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

_serviceProxy.Execute(impSolReq);

Het bijhouden van importsucces

Met de entiteit ImportJob kunt u gegevens vastleggen over het succes van het importeren van de oplossing. Wanneer u een ImportJobId opgeeft voor de ImportSolutionRequest, kunt u die waarde gebruiken om een query uit te voeren op de entiteit ImportJob over de status van de importbewerking. De ImportJobId kan ook worden gebruikt om een importlogboekbestand te downloaden met het bericht 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);
   }
}

De inhoud van de eigenschap Data is een tekenreeks die het XML-bestand van de oplossing weergeeft.

Oplossingsonderdelen toevoegen en verwijderen

Ontdek hoe u oplossingsonderdelen kunt toevoegen en verwijderen met behulp van code.

Een nieuw oplossingsonderdeel toevoegen

Dit voorbeeld laat zien hoe u een oplossingsonderdeel maakt dat is gekoppeld aan een specifieke oplossing. Als u het oplossingsonderdeel niet aan een specifieke oplossing koppelt wanneer het wordt gemaakt, wordt het oplossingsonderdeel alleen toegevoegd aan de standaardoplossing en moet u het handmatig toevoegen aan een oplossing of met behulp van de code die is opgenomen in Bestaand oplossingsonderdeel toevoegen.

Deze code maakt een nieuwe algemene optieset en voegt die toe aan de oplossing met een unieke naam die gelijk is aan _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);

Bestaand oplossingsonderdeel toevoegen

Dit voorbeeld toont hoe u een bestaand oplossingsonderdeel aan een oplossing toevoegt.

De volgende code gebruikt AddSolutionComponentRequest om de entiteit Account als oplossingsonderdeel aan een onbeheerde oplossing toe te voegen.

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

Een oplossingsonderdeel verwijderen

Dit voorbeeld toont hoe u een oplossingsonderdeel uit een onbeheerde oplossing verwijdert. De volgende code gebruikt RemoveSolutionComponentRequest om een entiteitsoplossingsonderdeel uit een onbeheerde oplossing te verwijderen. De solution.UniqueName verwijst naar de oplossing die is gemaakt in Een onbeheerde oplossing maken.

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

Een oplossing verwijderen

Het volgende voorbeeld laat zien hoe u een oplossing kunt ophalen met de oplossing uniquename en hoe u vervolgens solutionid uit de resultaten kunt extraheren. In het voorbeeld wordt de solutionid vervolgens gebruikt in combinatie met de IOrganizationService. De methode Delete om de oplossing te verwijderen.

// 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 en upgraden

U kunt extra oplossingsbewerkingen uitvoeren met behulp van de beschikbare API's. Gebruik voor het klonen en patchen van oplossingen de CloneAsPatchRequest en CloneAsSolutionRequest. Zie Oplossingspatches maken voor informatie over klonen en patchen.

Gebruik bij het uitvoeren van oplossingsupgrades de StageAndUpgradeRequest en DeleteAndPromoteRequest. Zie Een oplossing upgraden of updaten voor meer informatie over het proces van fasering en upgrades.

Afhankelijkheden van oplossingen detecteren

Dit voorbeeld toont hoe u een rapport maakt waarmee de afhankelijkheden tussen oplossingsonderdelen worden weergegeven.

Met deze code:

  • worden alle onderdelen voor een oplossing opgehaald.

  • worden alle afhankelijkheden voor elk onderdeel opgehaald.

  • wordt voor elke aangetroffen afhankelijkheeid een rapport weergegeven waarin de afhankelijkheid wordt beschreven.

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

De DependencyReport-methode is in het volgende codevoorbeeld.

Afhankelijkheidrapport

De DependencyReport-methode biedt een beschrijvender bericht op basis van informatie die zich in de afhankelijkheid bevindt.

Notitie

In dit voorbeeld wordt de methode slechts gedeeltelijk uitgevoerd. Het kan alleen berichten weergeven voor kenmerken en optiesetoplossingsonderdelen.

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

Ontdek of een oplossingsonderdeel kan worden verwijderd

Gebruik het bericht RetrieveDependenciesForDeleteRequest om andere oplossingsonderdelen te identificeren die zouden voorkomen dat een bepaald oplossingsonderdeel wordt verwijderd. In de volgende voorbeeldcode worden kenmerken gezocht met een bekende algemene optieset. Elk kenmerk dat de algemene optieset gebruikt, verhindert dat de algemene optieset wordt verwijderd.

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