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 deze bewerkingen en meer uitvoeren door de API's te gebruiken die voor u beschikbaar zijn. Hier 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
Meer informatie over hoe we enkele veelvoorkomende oplossingsbewerkingen kunnen uitvoeren met behulp van C#-code. Ga naar 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 tabel Uitgever wordt weergegeven. Een uitgever heeft de volgende eigenschappen nodig:
- Een aanpassingsvoorvoegsel
- Een unieke naam
- Een beschrijvende naam
Notitie
Gebruik voor een goede ALM-aanpak (Application Lifecycle Management) altijd een aangepaste uitgever en oplossing, niet de standaardoplossing en -uitgever, voor het implementeren van uw aanpassingen.
Met het volgende codevoorbeeld wordt eerst een uitgever gedefinieerd en vervolgens wordt gecontroleerd of de uitgever al bestaat op basis van de unieke naam. Als het al bestaat, is het aanpassingsvoorvoegsel mogelijk gewijzigd. Met dit voorbeeld wordt geprobeerd 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 kolommen met beschrijvingen van een oplossing.
Kolomlabel | Omschrijving |
---|---|
Weergavenaam | De naam van de oplossing. |
Meting | 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. |
Omschrijving | Gebruik deze kolom om relevante gegevens over uw oplossing op te nemen. |
Hier vindt u voorbeeldcode om een onbeheerde oplossing te maken die gebruikmaakt van de uitgever die we in het vorige gedeelte 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, voegt u oplossingsonderdelen toe 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 tabel 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 tabel 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.
In de volgende code wordt AddSolutionComponentRequest gebruikt om de tabel 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. In de volgende code wordt RemoveSolutionComponentRequest gebruikt om een tabeloplossingsonderdeel 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 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.
Deze code voert de volgende bewerkingen uit:
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 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);
}
Ontdekken 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 keuzekolom. Elk kenmerk dat de algemene keuze gebruikt, verhindert dat de algemene keuze 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);
}
}
}