Traballar con solucións mediante os SDK de Dataverse
Como parte do seu desenvolvemento para o ciclo de vida da produción, pode que queira crear unha automatización personalizada para xestionar certas tarefas. Por exemplo, na súa canle de proxectos de DevOps quizais desexe executar algún código ou script personalizado que cree un ambiente de illamento de procesos, importe unha solución non administrada, exporte esa solución sen xestionar como solución administrada e, finalmente, elimine o ambiente. Podes facer iso e moito máis usando as API que están dispoñibles. A continuación móstranse algúns exemplos do que pode realizar usando os SDK de Dataverse para .NET e o código personalizado.
Nota
Tamén pode realizar estas mesmas operacións empregando a API web. As accións relacionadas son: ImportSolution, ExportSolution, CloneAsPatch e CloneAsSolution.
Crear, exportar ou importar unha solución non xestionada
Vexamos como realizar algunhas operacións comúns de solucións mediante o código C#. Para ver a mostra completa de código C# de traballo que demostra este tipo de operacións de solucións (e máis), consulte Mostra: Traballar con solucións.
Crear un publicador
Todas as solucións requiren un publicador, representado pola entidade Publicador. Un publicador require o seguinte:
- Un prefixo de personalización
- Un nome único
- Un nome descritivo
Nota
Para un enfoque de ALM saudable, use sempre un publicador e unha solución personalizados, non a solución e o publicador predeterminados, para implementar as súas personalizacións.
A seguinte mostra de código define primeiro un publicador e, a continuación, verifica se o publicador xa existe segundo o nome único. Se xa existe, o prefixo de personalización puídose cambiar, polo que esta mostra busca capturar o prefixo de personalización actual. O PublisherId
tamén se captura para que se poida eliminar o rexistro do publicador. Se o publicador non se atopa, créase un novo publicador usando o IOrganizationService. Método de Creación.
// 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;
}
Crear unha solución non xestionada
Despois de ter dispoñible un publicador personalizado, pode crear unha solución sen xestionar. A seguinte táboa enumera os campos con descricións que contén unha solución.
Etiqueta de campo | Descrición |
---|---|
Nome para mostrar | Nome da solución. |
Nome | Microsoft Dataverse xera un nome único baseado no Nome de visualización. Pode editar o nome único. O nome único só pode conter caracteres alfanuméricos ou o carácter de guión baixo. |
Publicador | Use a busca de Publicador para asociar a solución a un publicador. |
Versión | Especifique unha versión usando o seguinte formato: principal.secundario.compilación.revisión (por exemplo 1.0.0.0. |
Páxina de configuración | Se inclúe un recurso Web HTML na súa solución, pode empregar esta busca para engadila como páxina de configuración da solución designada. |
Descripción | Use este campo para incluír todos os detalles relevantes sobre a súa solución. |
A continuación móstrase o código de exemplo para crear unha solución non xestionada que use o publicador que creamos na sección anterior.
// 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);
}
Despois de crear unha solución non xestionada, pode engadir compoñentes da solución creándoos no contexto desta solución ou engadindo compoñentes existentes doutras solucións. Máis información: Engadir un novo compoñente de solución e Engadir un compoñente de solución existente
Exportar unha solución non xestionada
Este exemplo de código mostra como exportar unha solución non administrada ou agrupar unha solución administrada. O código usa a clase ExportSolutionRequest para exportar un ficheiro comprimido que representa unha solución non xestionada. A opción para crear un solución administrada establécese mediante a propiedade Xestionado. Este exemplo garda un ficheiro chamado samplesolution.zip no cartafol de saída.
// 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);
Importar unha solución non xestionada
Importar (ou actualizar) unha solución usando código realízase con ImportSolutionRequest.
// Install or upgrade a solution
byte[] fileBytes = File.ReadAllBytes(ManagedSolutionLocation);
ImportSolutionRequest impSolReq = new ImportSolutionRequest()
{
CustomizationFile = fileBytes
};
_serviceProxy.Execute(impSolReq);
Seguimento do éxito de importación
Pode empregar a entidade ImportJob para capturar datos sobre o éxito da importación de solucións. Cando especifique unImportJobId
para a ImportSolutionRequest, pode usar este valor para consultar a entidade ImportJob sobre o estado da importación. O ImportJobId
tamén se pode usar para descargar un ficheiro de rexistro de importación coa mensaxe 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);
}
}
O contido da propiedade Data
é unha cadea que representa o ficheiro XML da solución.
Engadir e eliminar compoñentes da solución
Aprenda a engadir e eliminar os compoñentes da solución mediante código.
Engadir un novo compoñente da solución
Este exemplo mostra como crear un compoñente da solución asociado a unha solución específica. Se non asocia o compoñente da solución a unha solución específica cando se crea, só se engadirá á solución predeterminada e terá que engadila a unha solución manualmente ou usando o código incluído en Engadir un compoñente de solución existente.
Este código crea un novo conxunto de opcións global e engádeo á solución cun nome único igual 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);
Engadir un compoñente de solución existente
Este exemplo mostra como engadir un compoñente de solución existente a unha solución.
O seguinte código usa o AddSolutionComponentRequest para engadir a entidade Account
como compoñente de solución a unha solución non xestionada.
// 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);
Eliminar un compoñentes da solución
Este exemplo mostra como eliminar un compoñente de solución dunha solución non xestionada. O seguinte código usa o RemoveSolutionComponentRequest para eliminar un compoñente de solución de entidade dunha solución non xestionada. O solution.UniqueName
fai referencia á solución creada en Crea unha solución non xestionada.
// 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);
Eliminar unha solución
O seguinte exemplo mostra como recuperar unha solución usando a solución uniquename
e logo extrae a solutionid
a partir dos resultados. A mostra usa entón o solutionid
coIOrganizationService. Delete método para eliminar a solución.
// 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);
Clonacións, parches e actualizacións
Pode realizar operacións de solución adicionais empregando as API dispoñibles. Para solucións de clonación e parches, use o CloneAsPatchRequest e CloneAsSolutionRequest. Para obter información sobre a clonación e o parches, consulte Crear parches de solución.
Ao executar as actualizacións de solucións, use o StageAndUpgradeRequest e DeleteAndPromoteRequest. Para obter máis información sobre o proceso de escenificación e actualizacións, consulte Actualizar ou mellorar unha solución.
Detectar dependencias da solución
Este exemplo mostra como crear un informe que amosa as dependencias entre os compoñentes da solución.
Este código:
Recuperará todos os compoñentes para obter unha solución.
Recuperará todas as dependencias para cada compoñente.
Para cada dependencia atopada mostra un informe que describe a dependencia.
// 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);
}
}
O método DependencyReport
está na seguinte mostra de código.
Informe de dependencia
O método DependencyReport
proporciona unha mensaxe máis amable baseada na información atopada na dependencia.
Nota
Nesta mostra o método só se implementa parcialmente. Pode amosar mensaxes só para compoñentes de solución de atributos e conxunto de opcións.
/// <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);
}
Detectar se se poidan eliminar un compoñente da solución
Use a mensaxe RetrieveDependenciesForDeleteRequest para identificar calquera outro compoñente da solución que evitaría que se elimine un determinado compoñente de solución. O seguinte exemplo de código busca calquera atributo usando un conxunto de opcións global coñecido. Calquera atributo que use o conxunto de opcións global evitaría que se eliminase o conxunto global de opcións.
// 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);
}
}
}