Utilizzare soluzioni tramite Dataverse SDK

Durante lo sviluppo relativo al ciclo di vita della produzione, potresti voler creare un'automazione personalizzata per gestire determinate attività. Ad esempio, nella pipeline del progetto DevOps potresti voler eseguire codice o uno script personalizzato che crea un ambiente sandbox, importa una soluzione non gestita, esporta quella soluzione non gestita come soluzione gestita e, infine, elimina l'ambiente. Puoi eseguire tali attività e altre utilizzando le API disponibili. Di seguito sono riportati alcuni esempi di ciò che è possibile ottenere utilizzando Dataverse SDK per .NET e il codice personalizzato.

Nota

Puoi inoltre eseguire queste stesse operazioni utilizzando l'API Web. Le azioni correlate sono: ImportSolution, ExportSolution, CloneAsPatch, e CloneAsSolution.

Creare, esportare o importare una soluzione non gestita

Vediamo come eseguire alcune operazioni comuni relative alle soluzioni utilizzando codice C#. Per visualizzare l'esempio completo di codice C# funzionante che illustra questi tipi di operazioni relative alle soluzioni (e altro), vedi Esempio: utilizzare soluzioni.

Creare un autore

Ogni soluzione richiede un autore, rappresentato dall'entità Autore. Un autore richiede quanto segue:

  • Un prefisso di personalizzazione
  • Un nome univoco
  • Un nome descrittivo

Nota

Per un approccio ALM integro, utilizza sempre un autore e una soluzione personalizzati, non la soluzione e l'autore predefiniti, per distribuire le personalizzazioni.

Nell'esempio di codice seguente si definisce prima un autore e quindi si controlla se l'autore esiste già in base al nome univoco. Se esiste già, il prefisso di personalizzazione potrebbe essere stato modificato, pertanto l'esempio cerca di acquisire il prefisso di personalizzazione corrente. Anche PublisherId viene acquisito in modo da poter eliminare il record dell'autore. Se l'autore non viene trovato, viene creato un nuovo autore utilizzando il metodoI OrganizationService.Create.

// Define a new publisher
Publisher _myPublisher = new Publisher
{
   UniqueName = "contoso-publisher",
   FriendlyName = "Contoso publisher",
   SupportingWebsiteUrl =
      "https://video2.skills-academy.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;
}

Creare una soluzione non gestita

Dal momento in cui hai un autore personalizzato, puoi creare una soluzione non gestita. Nella tabella seguente sono elencati i campi con le descrizioni contenuti in una soluzione.

Etichetta campo Descrizione
Nome visualizzato Nome della soluzione.
Nome Microsoft Dataverse genera un nome univoco in base al Nome visualizzato. È possibile modificare il nome univoco. Il nome deve contenere solo caratteri alfanumerici o il carattere di sottolineatura.
Autore Utilizzare la ricerca Autore per associare la soluzione a un autore.
Versione Specifica una versione utilizzando il formato seguente: principale.secondaria.build.revisione, ad esempio: 1.0.0.0.
Pagina di configurazione Se includi una risorsa Web HTML nella soluzione, puoi utilizzare la ricerca per aggiungerla come pagina di configurazione della soluzione designata.
Descrizione Utilizzare questo campo per includere tutti i dettagli pertinenti sulla soluzione.

Di seguito è riportato un esempio di codice per creare una soluzione non gestita che utilizza l'autore che abbiamo creato nella sezione precedente.

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

Dopo aver creato una soluzione non gestita puoi aggiungere componenti di soluzione creandoli nel contesto di questa soluzione o aggiungendo componenti esistenti da altre soluzioni. Maggiori informazioni: Aggiungere un nuovo componente di soluzione e Aggiungere un componente di soluzione esistente

Esportare una soluzione non gestita

In questo esempio di codice viene illustrato come esportare una soluzione non gestita o comprimere una soluzione gestita. Il codice utilizza la classe ExportSolutionRequest per esportare un file compresso che rappresenta una soluzione non gestita. L'opzione per creare una soluzione gestita può essere impostata utilizzando la proprietà Managed. In questo esempio viene salvato un file denominato samplesolution.zip nella cartella di output.

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

Importare una soluzione non gestita

L'importazione (o l'aggiornamento) di una soluzione utilizzando codice viene eseguita con ImportSolutionRequest.

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

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

_serviceProxy.Execute(impSolReq);

Registrare l'esito dell'importazione

Puoi utilizzare l'entità ImportJob per acquisire i dati relativi all'esito dell'importazione della soluzione. Quando specifichi ImportJobId per ImportSolutionRequest, è possibile utilizzare quel valore per eseguire una query sull'entità ImportJob relativa allo stato dell'importazione. È possibile utilizzare ImportJobId anche per scaricare un file di registro dell'importazione mediante il messaggio 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);
   }
}

Il contenuto della proprietà Data è una stringa che rappresenta un file XML della soluzione.

Aggiungere e rimuovere componenti della soluzione

Scopri come aggiungere e rimuovere componenti della soluzione utilizzando il codice.

Aggiungere un nuovo componente di soluzione

In questo esempio viene illustrato come creare un componente di soluzione associato a una soluzione specifica. Se non associ il componente di soluzione a una soluzione specifica quando viene creato, verrà solo aggiunto alla soluzione predefinita e sarà necessario aggiungerlo a una soluzione manualmente o tramite il codice incluso in Aggiungere un componente di soluzione esistente.

Questo codice crea un nuovo set di opzioni globale e lo aggiunge alla soluzione con nome univoco uguale a _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);

Aggiungere un componente di soluzione esistente

In questo esempio viene illustrato come aggiungere un componente di soluzione esistente a una soluzione.

Il codice seguente utilizza AddSolutionComponentRequest per aggiungere l'entità Account come componente di soluzione a una soluzione non gestita.

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

Rimuovere un componente di soluzione

In questo esempio viene illustrato come rimuovere un componente di soluzione da una soluzione non gestita. Il codice seguente utilizza RemoveSolutionComponentRequest per rimuovere un componente di soluzione dell'entità da una soluzione non gestita. L'elemento solution.UniqueName fa riferimento alla soluzione creata in Creare una soluzione non gestita.

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

Eliminare una soluzione

Nell'esempio seguente viene illustrato come recuperare una soluzione utilizzando la soluzione uniquename ed estrarre l'elemento solutionid dai risultati. L'esempio utilizza quindi l'elemento solutionid con IOrganizationService. Il metodo Delete per eliminare la soluzione.

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

Clonazione, patch e aggiornamento

Puoi eseguire operazioni aggiuntive sulla soluzione utilizzando le API disponibili. Per le soluzioni di clonazione e applicazione di patch utilizza CloneAsPatchRequest e CloneAsSolutionRequest. Per informazioni su clonazione e patch, consulta Creare patch per soluzioni.

Quando si eseguono aggiornamenti della soluzione, utilizza StageAndUpgradeRequest e DeleteAndPromoteRequest. Per ulteriori informazioni sul processo di gestione temporanea e aggiornamenti, vedi Aggiornare una soluzione.

Rilevare le dipendenze delle soluzioni

In questo esempio viene illustrato come creare una report che indica le dipendenze tra i componenti di soluzione.

Con questo codice sarà possibile:

  • Recuperare tutti i componenti per una soluzione.

  • Recuperare tutte le dipendenze per ogni componente.

  • Per ogni dipendenza trovata, visualizzare un report che descrive la dipendenza.

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

Il metodo DependencyReport è nel seguente esempio di codice.

Report di dipendenza

Il metodo DependencyReport fornisce un messaggio più semplice da usare in base alle informazioni disponibili nella dipendenza.

Nota

In questo esempio il metodo è solo parzialmente implementato. Vengono visualizzati messaggi solo per i componenti di soluzione di un set di opzioni e di attributo.

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

Rilevare se un componente di soluzione potrebbe essere stato eliminato

Utilizzare il messaggio RetrieveDependenciesForDeleteRequest per identificare tutti i componenti di soluzione che impedirebbero a un componente di soluzione specificato di essere eliminato. Il seguente esempio di codice cerca gli attributi tramite un noto set di opzioni globale. Qualsiasi attributo che utilizza il set di opzioni globale impedirebbe al set di opzioni globale da essere eliminato.

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