Arbeta med lösningar med hjälp av Dataverse SDK
Som en del av utvecklingen av produktions-livscykeln kan du skapa en anpassad automatisering för att hantera vissa uppgifter. Du kanske till exempel vill köra anpassad kod eller skript i en DevOps-projektpipeline som skapar en sandbox-miljö, importerar en icke-hanterad lösning, exporterar den icke-hanterade lösningen som en hanterad lösning och slutligen tar bort miljön. Du kan göra detta och mycket mer med hjälp av de API:er som är tillgängliga för dig. Nedan följer några exempel på vad du kan göra med Dataverse SDK för .NET och anpassad kod.
Kommentar
Du kan även utföra samma åtgärder med webb-API:t. De relaterade åtgärderna är: ImportSolution, ExportSolution, CloneAsPatch och CloneAsSolution.
Skapa, exportera eller importera en icke-hanterad lösning
Nu ska vi se hur du kan utföra vanliga lösningsåtgärder med hjälp av C#-kod. Om du vill visa det fullständiga C#-kodexemplet som demonstrerar dessa typer av lösningsfunktioner (med mera), se exempel: arbeta med lösningar.
Skapa en utgivare
För varje lösning krävs en utgivare som representeras aventiteten Utgivare. En utgivare kräver följande:
- Ett prefix för anpassning
- Ett unikt namn
- Ett användarvänligt namn
Kommentar
Om du vill ha en felfri ALM-metod bör du alltid använda en anpassad utgivare och lösning, inte standardlösningen och -utgivaren, för att distribuera anpassningar.
Följande exempelkod definierar först en utgivare och kontrollerar sedan om utgivaren redan finns på grundval av det unika namnet. Om det redan finns kan det hända att anpassningsprefixet har ändrats, varför detta exempel försöker registrera det aktuella anpassningsprefixet. PublisherId
registreras också så att utgivarposten kan tas bort. Om utgivaren inte hittas skapas en ny utgivare med hjälp av metoden IOrganizationService.Skapa.
// 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;
}
Skapa en icke-hanterad lösning
När du har en anpassad utgivare tillgänglig kan du skapa en icke-hanterad lösning. I följande tabell visas de fält som innehåller de beskrivningar som en lösning innehåller.
Etikett för fält | Beskrivning |
---|---|
Visningsnamn | Namnet på lösningen. |
Namn | Microsoft Dataverse genererar ett unikt namn baserat påVisningsnamn. Du kan redigera det unika namnet. Det unika namnet får endast innehålla alfanumeriska tecken och understreck. |
Utgivare | Använd sökningen Utgivare för att associera lösningen med en utgivare. |
Version | Ange en version med följande format: major.minor.build.revision (till exempel 1.0.0.0. |
Konfigurationssida | Om du lägger till en HTML-webbresurs i din lösning kan du använda den här sökningen för att lägga till den som din angivna lösningskonfigurationssida. |
Beskrivning | Använd det här fältet om du vill ta med all relevant information om lösningen. |
Nedan visas exempelkod för hur du skapar en icke-hanterad lösning som använder den utgivare som vi skapade i föregående avsnitt.
// 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 skapat en icke-hanterad lösning kan du lägga till komponenter genom att skapa dem inom ramarna för denna lösning eller genom att lägga till befintliga komponenter från andra lösningar. Mer information: Lägg till en ny komponent och Lägg till en befintlig komponent
Exportera en icke-hanterad lösning
I det här kodexemplet visas hur du exporterar en icke-hanterad lösning eller paketerar en hanterad lösning. I koden används klassen ExportSolutionRequest för att exportera en komprimerad fil som representerar en icke-hanterad lösning. Alternativet att skapa en hanterad lösning anges med egenskapen Hanterad. I det här exemplet sparas en fil med namnet samplesolution.zip i mappen Utdata.
// 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);
Importera en icke-hanterad lösning
Att importera (eller uppgradera) en lösning med hjälp av kod sker med ImportSolutionRequest.
// Install or upgrade a solution
byte[] fileBytes = File.ReadAllBytes(ManagedSolutionLocation);
ImportSolutionRequest impSolReq = new ImportSolutionRequest()
{
CustomizationFile = fileBytes
};
_serviceProxy.Execute(impSolReq);
Spåra importstatus
Du kan använda entiteten ImportJob för att samla in data om lösningsimportens status. När du anger ett ImportJobId
för ImportSolutionRequest kan du använda det värdet för att fråga entiteten ImportJob om statusen för importen. ImportJobId
kan också användas för att hämta en importloggfil med hjälp av meddelandet 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);
}
}
Innehållet i egenskapen Data
är en sträng som representerar XML-lösningsfilen.
Lägg till och ta bort komponenter
Lär dig hur du lägger till och tar bort komponenter med hjälp av kod.
Lägg till en ny komponent
I det här exemplet visas hur du skapar en komponent som är associerad med en specifik lösning. Om du inte associerar komponenten med en specifik lösning när den skapas läggs den bara till i standardlösningen, och du måste då lägga till den i en lösning manuellt eller genom att använda koden som ingår i komponenten Lägg till en befintlig lösning.
Den här koden skapar en ny global alternativuppsättning och lägger till den i lösningen med ett unikt namn som är lika 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);
Lägg till en befintlig komponent
I det här exemplet visas hur du lägger till en befintlig komponent i en lösning.
I följande kod används AddSolutionComponentRequest för att lägga till Account
-entiteten som en komponent i en icke-hanterad 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);
Tar bort en komponent
I det här exemplet visas hur du tar bort en befintlig komponent från en icke-hanterad lösning. Följande kod använder RemoveSolutionComponentRequest för att ta bort en entitetskomponent från en icke-hanterad lösning. solution.UniqueName
refererar till den lösning som skapades i Skapa en icke-hanterad 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);
Ta bort en lösning
Följande exempel visar hur du hämtar en lösning med hjälp av lösningen uniquename
och sedan extraherar solutionid
från resultaten. I exemplet används sedan solutionid
medIOrganizationService. Delete metod för att ta bort 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);
Klona, korrigera och uppgradera
Du kan utföra ytterligare lösningsåtgärder med hjälp av tillgängliga API:er. För klonings- och korrigeringslösningar används CloneAsPatchRequest och CloneAsSolutionRequest. Information om kloning och korrigering finns i Skapa lösningskorrigeringar.
När du utför lösningsuppgraderingar använder du StageAndUpgradeRequest och DeleteAndPromoteRequest. Mer information om hur du mellanlagrar och uppgraderar finns i Uppgradera eller uppdatera en lösning.
Identifiera lösningsberoenden
I det här exemplet visas hur du skapar en rapport som visar beroendena mellan komponenterna.
Den här koden kommer att:
Hämta alla komponenter för en lösning.
Hämta alla beroenden för respektive komponent.
För varje beroende som hittats visas en rapport som beskriver beroendet.
// 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
finns i följande kodexempel.
Beroenderapport
Metoden DependencyReport
ger ett vänligare meddelande utifrån den information som finns i beroendet.
Kommentar
I det här exemplet implementeras metoden endast delvis. Det går endast att visa meddelanden för komponenter för attribut och alternativuppsättningar.
/// <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);
}
Identifiera huruvida en komponent som kan raderas
Använd RetrieveDependenciesForDeleteRequest-meddelandet för att identifiera andra eventuella komponenter som kan förhindra att en viss komponent tas bort. Följande kodexempel söker efter attribut med hjälp av en känd global alternativuppsättning. Alla attribut som använder den globala alternativuppsättningen förhindrar att den globala alternativuppsättningen tas bort.
// 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);
}
}
}