Treballar amb solucions mitjançant l'SDK del Dataverse
Com a part del desenvolupament al cicle de vida de producció, pot ser que vulgueu crear una automatització personalitzada per gestionar determinades tasques. Per exemple, al pipeline del projecte del DevOps, pot ser que vulgueu executar algun codi o script personalitzat que creï un entorn d'espai aïllat, importi una solució no administrada, exporti aquesta solució no administrada com a solució administrada i, per últim, suprimeixi l'entorn. Podeu fer tot això i més accions amb les API que hi ha a la vostra disposició. A continuació hi ha alguns exemples que podeu dur a terme amb l'SDK del Dataverse per a .NET i el codi personalitzat.
Nota
També podeu dur a terme aquestes mateixes operacions amb l'API web. Les accions relacionades són: ImportSolution, ExportSolution, CloneAsPatch i CloneAsSolution.
Crear, exportar o importar una solució no administrada
Vegem com dur a terme algunes operacions de la solució habituals mitjançant codi C#. Per visualitzar l'exemple del codi C# operatiu complet que demostra aquest tipus d'operacions de la solució (i altres), vegeu Exemple: Treballar amb solucions.
Crear un editor
Cada solució necessita un editor, representat per l'entitat Editor. Un editor necessita el següent:
- Un prefix de personalització
- Un nom únic
- Un nom descriptiu
Nota
Per al mètode d'ALM en bon estat, utilitzeu sempre una solució i un editor personalitzats, en lloc de la solució i l'editor per defecte, per implementar les personalitzacions.
El següent exemple de codi defineix primer un editor i després comprova si l'editor ja existeix en funció del nom únic. Si ja existeix, pot ser que el prefix de personalització s'hagi canviat, de manera que l'exemple intenta capturar el prefix de personalització actual. També es captura el PublisherId
per tal que el registre de l'editor pugui suprimir-se. Si no es troba l'editor, se'n crea un de nou mitjançant el mètode 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;
}
Crear una solució no administrada
Després de tenir un editor personalitzat disponible, podreu crear una solució no administrada. A la taula següent s'enumeren els camps amb descripcions que conté una solució.
Etiqueta de camp | Descripció |
---|---|
Nom de visualització | El nom de la solució. |
Nom | El Microsoft Dataverse genera un nom únic en funció del Nom de visualització. Podeu editar el nom únic. El nom únic només ha de contenir caràcters alfanumèrics o de subratllat. |
Editor | Utilitzeu la cerca Editor per associar la solució a un editor. |
Versió | Especifiqueu una versió amb el format següent: superior.inferior.compilació.revisió, per exemple, 1.0.0.0. |
Pàgina de configuració | Si incloeu un recurs web HTML a la solució, podeu utilitzar aquesta cerca per afegir-la com a pàgina de configuració de la solució designada. |
Descripció | Utilitzeu aquest camp per incloure els detalls rellevants sobre la solució. |
A continuació es mostra el codi d'exemple per crear una solució no administrada que utilitza l'editor que hem creat a la secció 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);
}
Després de crear una solució no administrada, podeu afegir-hi components de la solució. Per fer-ho, creeu-los en el context d'aquesta solució o afegiu-hi components existents des d'altres solucions. Més informació: Afegir un component de la solució nou i Afegir un component de la solució existent
Exportar una solució no administrada
Aquest codi d'exemple mostra com exportar una solució no administrada o empaquetar una solució administrada. El codi utilitza la classe ExportSolutionRequest per exportar un fitxer comprimit que representa una solució no administrada. L'opció per crear una solució administrada es defineix amb la propietat Administrada. Aquest exemple desa un fitxer anomenat samplesolution.zip a la carpeta de sortida.
// 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 una solució no administrada
La importació (o actualització) d'una solució mitjançant el codi s'aconsegueix amb ImportSolutionRequest.
// Install or upgrade a solution
byte[] fileBytes = File.ReadAllBytes(ManagedSolutionLocation);
ImportSolutionRequest impSolReq = new ImportSolutionRequest()
{
CustomizationFile = fileBytes
};
_serviceProxy.Execute(impSolReq);
Importació del seguiment correcta
Podeu utilitzar l'entitat ImportJob per capturar dades sobre l'èxit de la importació de la solució. Quan especifiqueu ImportJobId
per a l'element ImportSolutionRequest, podeu utilitzar aquest valor per consultar l'entitat ImportJob sobre l'estat de la importació. També es pot utilitzar ImportJobId
per baixar un fitxer de registre d'importació mitjançant el missatge 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);
}
}
El contingut de la propietat Data
és una cadena que representa el fitxer XML de la solució.
Afegir i suprimir components de la solució
Més informació sobre com afegir i suprimir components de la solució mitjançant codi.
Afegir un component de la solució nou
En aquest exemple es mostra com es crea un component de la solució associat a una solució concreta. Si no associeu el component de la solució a una solució concreta quan es creï, només s'afegirà a la solució per defecte i l'haureu d'afegir a una solució manualment o mitjançant el codi inclòs a Afegir un component de la solució existent.
Aquest codi crea un conjunt d'opcions global nou i l'afegeix a la solució amb un nom únic que equival 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);
Afegir un component de la solució existent
En aquest exemple es mostra com s'afegeix un component de la solució existent a una solució.
El codi següent utilitza AddSolutionComponentRequest per afegir l'entitat Account
com a component de la solució a una solució no administrada.
// 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);
Suprimir un component de la solució
En aquest exemple es mostra com se suprimeix un component de la solució des d'una solució no administrada. El codi següent utilitza RemoveSolutionComponentRequest per suprimir un component de la solució de l'entitat des d'una solució no administrada. El solution.UniqueName
fa referència a la solució creada a Crear una solució no administrada.
// 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);
Suprimir una solució
En l'exemple següent es mostra com es recupera una solució mitjançant el uniquename
de la solució i, a continuació, s'extreu solutionid
dels resultats. A continuació, la mostra utilitza solutionid
amb IOrganizationService. Delete per suprimir la solució.
// 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ó, pedaç i actualització
Per dur a terme operacions de la solució addicionals, utilitzeu les API disponibles. En el cas de solucions de clonació i pedaços, utilitzeu CloneAsPatchRequest i CloneAsSolutionRequest. Per obtenir informació sobre la clonació i el pedaç, vegeu Crear pedaços de solució.
En actualitzar la solució, utilitzeu StageAndUpgradeRequest i DeleteAndPromoteRequest. Per obtenir més informació sobre el procés de fases i les millores, vegeu Actualitzar una solució.
Detectar dependències de la solució
En aquest exemple es mostra com es crea un informe que mostra les dependències entre els components de la solució.
Aquest codi permetrà:
Recuperar tots els components d'una solució.
Recuperar totes les dependències de cada component.
Mostrarà un informe que descriurà la dependència per a cada dependència trobada.
// 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);
}
}
El mètode DependencyReport
és a la següent mostra de codi.
Informe de dependència
El mètode DependencyReport
proporciona un missatge més descriptiu en funció de la informació trobada a la dependència.
Nota
En aquesta mostra, el mètode només s'implementa parcialment. Només pot mostrar missatges per als components de la solució d'atribut i conjunt d'opcions.
/// <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);
}
Detecta si es pot suprimir un component de la solució
Utilitzeu el missatge RetrieveDependenciesForDeleteRequest per identificar qualsevol altre component de la solució que podria impedir que se suprimeixi un determinat component de la solució. La mostra de codi següent cerca qualsevol atribut que utilitzi un conjunt d'opcions global conegut. Qualsevol atribut que utilitzi el conjunt d'opcions global impedirà que se suprimeixi el conjunt d'opcions global.
// 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);
}
}
}