Arbejde med løsninger ved hjælp af Dataverse-SDK
Som en del af din udvikling af produktionslivscyklussen ønsker du måske at oprette brugerdefineret automatisering til håndtering af bestemte opgaver. I din DevOps-projektpipeline ønsker du f.eks. måske at udføre en brugerdefineret kode eller et script, der opretter et sandkassemiljø, importere en ikke-administreret løsning, eksportere denne ikke-administrerede løsning som en administreret løsning og endelig slette miljøet. Du kan gøre dette og meget mere ved at bruge de API'er, der er tilgængelige for dig. Nedenfor vises nogle eksempler på, hvad du kan opnå ved hjælp af Dataverse-SDK til .NET og brugerdefineret kode.
Bemærk
Du kan også udføre de samme handlinger ved hjælp af Web-API'en. De relaterede handlinger er: ImportSolution, ExportSolution, CloneAsPatch og CloneAsSolution.
Oprette, eksportere eller importere en ikke-administreret løsning
Her kan du se, hvordan du udfører nogle almindelige løsningshandlinger ved hjælp af C#-koden. Hvis du vil have vist det fuldstændigt fungerende C#-kodeeksempel, der demonstrerer disse typer løsningshandlinger (og meget mere), kan du se Eksempel: Arbejde med løsninger.
Oprette en udgiver
Hver løsning kræver en udgiver, der er repræsenteret af Publisher-objektet. En udgiver kræver følgende:
- Et tilpasningspræfiks
- Et entydigt navn
- Et fuldt navn
Bemærk
Hvis du vil opnå en god ALM-tilgang, skal du altid bruge en brugerdefineret udgiver og løsning, ikke standardløsningen og -udgiveren, til at implementere tilpasningerne.
Følgende kodeeksempel definerer først en udgiver og kontrollerer derefter, om udgiveren allerede findes, baseret på det entydige navn. Hvis udgiveren allerede findes, kan det være, at tilpasningspræfikset er blevet ændret, så dette eksempel søger at hente det aktuelle tilpasningspræfiks. PublisherId
hentes også, så udgiverposten kan slettes. Hvis udgiveren ikke findes, oprettes en ny publisher ved hjælp af IOrganizationService.Create-metoden.
// 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;
}
Oprette en ikke-administreret løsning
Når en brugerdefineret udgiver er tilgængelig for dig, kan du oprette en ikke-administreret løsning. I følgende tabel vises de felter med beskrivelser, som en løsning indeholder.
Feltetiket | Beskrivelse |
---|---|
Vist navn | Navnet på løsningen. |
Navn | Microsoft Dataverse genererer et entydigt navn baseret på Vist navn. Du kan redigere det entydige navn. Det entydige navn må kun indeholde alfanumeriske tegn og understregningstegnet. |
Udgiver | Brug Udgiver-opslaget til at knytte en udgiver til en løsning. |
Version | Angiv en version ved brug af følgende format: major.minor.build.revision (f.eks. 1.0.0.0.) |
Konfigurationsside | Hvis du medtager en HTML-webressource i din løsning, kan du bruge dette opslag til at tilføje den som din udpegede konfigurationsside for løsningen. |
Beskrivelse | Brug dette felt til at medtage alle relevante oplysninger om din løsning. |
Herunder finder du en eksempelkode, der opretter en ikke-administreret løsning, som bruger den udgiver, vi har oprettet i forrige afsnit.
// 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);
}
Når du har oprettet en ikke-administreret løsning, kan du tilføje løsningskomponenter ved at oprette dem i konteksten af denne løsning eller ved at tilføje eksisterende komponenter fra andre løsninger. Flere oplysninger: Tilføjelse en ny løsningskomponent og Tilføjelse af en eksisterende løsningskomponent
Eksport af en ikke-administreret løsning
I dette kodeeksempel vises, hvordan du eksporterer en ikke-administreret løsning eller pakker en administreret løsning. Koden bruger ExportSolutionRequest-klassen til at eksportere en komprimeret fil, der repræsenterer en ikke-administreret løsning. Muligheden for at oprette en administreret løsning er angivet ved hjælp af egenskaben Administreret. I dette eksempel gemmes en fil med navnet samplesolution.zip i outputmappen.
// 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);
Import af en ikke-administreret løsning
Import (eller opgradering) af en løsning ved hjælp af kode udføres med ImportSolutionRequest.
// Install or upgrade a solution
byte[] fileBytes = File.ReadAllBytes(ManagedSolutionLocation);
ImportSolutionRequest impSolReq = new ImportSolutionRequest()
{
CustomizationFile = fileBytes
};
_serviceProxy.Execute(impSolReq);
Sporing af importudfald
Du kan bruge ImportJob-objektet til at registrere data om udfaldet af importen af løsningen. Når du angiver et ImportJobId
for ImportSolutionRequest, kan du bruge denne værdi til at anmode ImportJob-objektet om statussen for importen. ImportJobId
kan også bruges til at overføre en importlogfil ved hjælp af meddelelsen 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);
}
}
Indholdet af Data
-egenskaben er en streng, der repræsenterer løsningens XML-fil.
Tilføje og fjerne løsningskomponenter
Få mere at vide om, hvordan du tilføjer og fjerner løsningskomponenter ved hjælp af kode.
Tilføje en ny løsningskomponent
I dette eksempel vises, hvordan du opretter en løsningskomponent, der er knyttet til en specifik løsning. Hvis du ikke knytter løsningskomponenten til en bestemt løsning, når den oprettes, vil den kun blive føjet til standardløsningen, og du skal føje den til en løsning manuelt eller ved hjælp af den kode, der er inkluderet i Tilføje en eksisterende løsningskomponent. .
Denne kode opretter en ny global grupperet indstilling og føjer den til løsningen med et entydigt navn, der er lig med _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);
Tilføje en eksisterende løsningskomponent
I dette eksempel vises, hvordan du føjer en eksisterende løsningskomponent til en løsning.
Følgende kode bruger AddSolutionComponentRequest til at tilføje Account
-objektet som en løsningskomponent til en ikke-administreret løsning.
// 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);
Fjerne en løsningskomponent
I dette eksempel vises, hvordan du fjerner en løsningskomponent fra en ikke-administreret løsning. Følgende kode bruger RemoveSolutionComponentRequest til at fjerne en objektløsningskomponent fra en ikke-administreret løsning. solution.UniqueName
refererer til løsningen, der er oprettet i Oprette en ikke-administreret løsning.
// 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);
Slette en løsning
Følgende eksempel viser, hvordan du henter en løsning ved hjælp af løsningens uniquename
og derefter pakker solutionid
ud af resultaterne. I eksemplet bruges solutionid
sammen med IOrganizationService. Delete metode til at slette løsningen.
// 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);
Kloning, rettelse og opgradering
Du kan udføre yderligere løsningshandlinger ved hjælp af de tilgængelige API'er. Til klonings- og rettelsesløsninger skal du bruge CloneAsPatchRequest og CloneAsSolutionRequest. Du kan finde oplysninger om kloning og programrettelser under Opret løsningsprogramrettelser.
Når du udfører løsningsopgraderinger, skal du bruge StageAndUpgradeRequestog DeleteAndPromoteRequest. Du kan finde flere oplysninger om processen til midlertidig oplagring og opgraderinger under Opgradering eller opdatering af en løsning.
Opdage løsningsafhængigheder
I dette eksempel vises, hvordan du opretter en rapport, der viser afhængigheder mellem løsningskomponenter.
Denne kode vil:
Hente alle komponenter til en løsning.
Hente alle afhængigheder for hver komponent.
Vise en rapport, der beskriver afhængigheden, for hver afhængighed, der er fundet.
// 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);
}
}
Metoden DependencyReport
findes i følgende kodeeksempel.
Afhængighedsrapport
Metoden DependencyReport
giver en mere læsevenlig meddelelse, der er baseret på oplysninger, der findes inden for afhængigheden.
Bemærk
I dette eksempel er metoden kun delvist implementeret. Den kan kun vise meddelelser for attributters og grupperede indstillingers løsningskomponenter.
/// <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);
}
Opdage, om en løsningskomponent må slettes
Brug RetrieveDependenciesForDeleteRequest-meddelelsen til at identificere eventuelle andre løsningskomponenter, der kunne forhindre, at en bestemt løsningskomponent bliver slettet. Følgende kodeeksempel søger efter attributter ved hjælp af en kendt global grupperet indstilling. Enhver attribut, der bruger den globale grupperede indstilling, vil forhindre den globale grupperede indstilling i at blive slettet.
// 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);
}
}
}