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.
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.
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://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;
}
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 del 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
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);
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);
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.
Scopri come aggiungere e rimuovere componenti della soluzione utilizzando il codice.
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);
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);
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);
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. Delete metodo 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);
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.
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.
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);
}
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);
}
}
}