Arbeide med løsninger ved hjelp av Dataverse SDK
Som en del av utviklingen til produksjonslivssyklusen kan det hende du vil opprette egendefinerte automatiseringsoppgaver for å håndtere visse oppgaver. I DevOps-prosjektforløpet ditt kan det for eksempel hende at du vil kjøre noe egendefinert kode eller skript som oppretter et sandkassemiljø, importerer en uadministrert løsning, eksporterer denne uadministrerte løsningen som en administrert løsning og til slutt sletter miljøet. Du kan gjøre dette og mer ved å bruke API-ene som er tilgjengelige for deg. Nedenfor finner du noen eksempler på hva du kan oppnå ved hjelp av Dataverse SDK for .NET og egendefinert kode.
Obs!
Du kan også utføre de samme operasjonene ved hjelp av Web API. De relaterte handlingene er: ImportSolution, ExportSolution, CloneAsPatch og CloneAsSolution.
La oss se hvordan du utfører enkelte felles løsningsoperasjoner ved hjelp av C#-kode. Hvis du vil vise hele det fungerende C#-kodeeksemplet som demonstrerer slike løsningsoperasjoner (og mer), kan du se Eksempel: Arbeide med løsninger.
Hver løsning krever en utgiver som representeres av Utgiver-enheten. En utgiver krever følgende:
- Et tilpassingsprefiks
- Et unikt navn
- Et egendefinert navn
Obs!
Hvis du vil ha en god ALM-tilnærming, må du alltid bruke en egendefinert utgiver og løsning, ikke standardløsningen og -utgiveren, for distribusjon av tilpassingene.
Følgende kodeeksempel definerer først en utgiver og kontrollerer deretter om utgiveren finnes, basert på det unike navnet. Hvis den finnes fra før, kan det hende at tilpassingsprefikset er endret, slik at dette eksemplet forsøker å fange opp det gjeldende tilpassingsprefikset. PublisherId
registreres også, slik at utgiveroppføringen kan slettes. Hvis utgiveren ikke blir funnet, opprettes det en ny utgiver ved hjelp av 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;
}
Når du har en egendefinert utgiver tilgjengelig, kan du opprette en uadministrert løsning. Tabellen nedenfor viser feltene med beskrivelser som en løsning inneholder.
Felt Etikett | Beskrivelse |
---|---|
Visningsnavn | Navnet på løsningen. |
Navn | Microsoft Dataverse genererer et unikt navn basert på visningsnavnet. Du kan redigere det unike navnet. Det unike navnet må bare inneholde alfanumeriske tegn og understrekingstegnet. |
Utgiver | Bruk Utgiver-oppslaget til å knytte løsningen til en utgiver. |
Versjon | Angi en versjon med følgende format: major.minor.build.revision (for eksempel 1.0.0.0). |
Konfigurasjonsside | Hvis du inkluderer en HTML-webressurs i løsningen, kan du bruke dette oppslaget til å legge den til som den angitte løsningskonfigurasjonssiden. |
Beskrivelse | Bruk dette feltet til å inkludere alle relevante detaljer om løsningen. |
Nedenfor finner du eksempelkode for å opprette en uadministrert løsning som bruker utgiveren vi opprettet i forrige del.
// 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 opprettet en uadministrert løsning, kan du legge til løsningskomponenter ved å opprette dem i konteksten for denne løsningen eller ved å legge til eksisterende komponenter fra andre løsninger. Mer informasjon: Legge til en ny løsningskomponent og Legge til en eksisterende løsningskomponent
Dette kodeeksemplet viser hvordan du eksporterer en uadministrert løsning eller pakker en administrert løsning. Koden bruker klassen ExportSolutionRequest til å eksportere en komprimert fil som representerer en uadministrert løsning. Alternativet for å opprette en administrert løsning angis ved hjelp av den administrerte-egenskapen. Dette eksemplet lagrer en fil med navnet samplesolution.zip i utdatamappen.
// 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 (eller oppgradering) av en løsning ved hjelp av kode oppnås ved hjelp av ImportSolutionRequest.
// Install or upgrade a solution
byte[] fileBytes = File.ReadAllBytes(ManagedSolutionLocation);
ImportSolutionRequest impSolReq = new ImportSolutionRequest()
{
CustomizationFile = fileBytes
};
_serviceProxy.Execute(impSolReq);
Du kan bruke ImportJob-enheten til å registrere data om hvor vellykket løsningsimporten er. Når du angir en ImportJobId
for ImportSolutionRequest, kan du bruke den verdien til å spørre ImportJob-enheten om statusen for importen. ImportJobId
kan også brukes til å laste ned en importloggfil ved hjelp av RetrieveFormattedImportJobResultsRequest-meldingen.
// 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);
}
}
Innholdet i Data
-egenskapen er en streng som representerer XML-filen for løsningen.
Lær hvordan du legger til og fjerner løsningskomponenter ved hjelp av kode.
Dette eksemplet viser hvordan du oppretter en løsningskomponent som er tilknyttet en bestemt løsning. Hvis du ikke knytter løsningskomponenten til en bestemt løsning når den opprettes, blir den bare lagt til i standardløsningen, og du må legge den til i en løsning manuelt eller ved å bruke koden som er inkludert i Legge til en eksisterende løsningskomponent.
Denne koden oppretter et nytt globalt alternativsett og legger det til i løsningen med et unikt navn lik _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);
Dette eksemplet viser hvordan du legger til en eksisterende løsningskomponent i en løsning.
Følgende kode bruker AddSolutionComponentRequest til å legge til Account
-enheten som en løsningskomponent i en uadministrert 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);
Dette eksemplet viser hvordan du fjerner en løsningskomponent fra en uadministrert løsning. Følgende kode bruker RemoveSolutionComponentRequest til å fjerne en enhetsløsningskomponent fra en uadministrert løsning. solution.UniqueName
refererer til løsningen som ble opprettet i Opprette en uadministrert 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);
Følgende eksempel viser hvordan du henter en løsning ved hjelp av løsningen uniquename
og deretter pakker ut solutionid
fra resultatene. Eksemplet bruker deretter solutionid
med IOrganizationService. Delete metode for å 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);
Du kan utføre flere løsningsoperasjoner ved å bruke de tilgjengelige APIene. For klonings- og oppdateringsløsninger bruker du CloneAsPatchRequest og CloneAsSolutionRequest. Hvis du vil ha informasjon om kloning og oppdatering, kan du se Opprette løsningsoppdateringer.
Ved utføring av løsningsoppgraderinger bruker du StageAndUpgradeRequest og DeleteAndPromoteRequest. Hvis du vil ha mer informasjon om prosessen med oppsamling og oppgradering, kan du se Oppgradere eller oppdatere en løsning.
Dette eksemplet viser hvordan du oppretter en rapport som viser avhengighetene mellom løsningskomponenter.
Denne koden gjør følgende:
Henter alle komponentene for en løsning.
Henter alle avhengighetene for hver komponent.
For hver avhengighet som blir funnet, vises en rapport som beskriver avhengigheten.
// 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);
}
}
DependencyReport
-metoden finnes i følgende kodeeksempel.
DependencyReport
-metoden gir en lesevennlig melding basert på informasjon som finnes innenfor avhengigheten.
Obs!
I dette eksemplet er metoden bare delvis implementert. Den kan bare vise meldinger for løsningskomponenter for attributter og alternativsett.
/// <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);
}
Bruk RetrieveDependenciesForDeleteRequest-meldingen til å identifisere andre løsningskomponenter som kan hindre at en gitt løsningskomponent slettes. Følgende kodeeksempel ser etter attributter ved hjelp av et kjent globalt alternativsett. Alle attributter som bruker det globale alternativsettet, hindrer at det globale alternativsettet slettes.
// 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);
}
}
}