Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Som en del av utvecklingen av produktionslivscykeln kanske du vill 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 utföra dessa åtgärder och många fler med hjälp av de API:er som är tillgängliga för dig. Här 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.
Kodexemplen i den här artikeln använder tidiga entitetstyper som genereras med hjälp av antingen CrmSvcUtil eller PAC CLI. Mer information: Programmering med sen bindning och tidig bindning med hjälp av SDK för .NET
Skapa, exportera eller importera en icke-hanterad lösning
Lär dig mer om 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), gå till Exempel: Arbeta med lösningar.
Skapa en utgivare
För varje lösning krävs en utgivare som representeras av tabellen Utgivare. En utgivare kräver följande egenskaper:
- Ett prefix för anpassning
- Ett unikt namn
- Ett användarvänligt namn
Kommentar
Om du vill ha en felfri ALM-metod (hantering av programlivscykel) 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 huruvida utgivaren redan finns på grundval av det unika namnet. Om det redan finns kan anpassningsprefixet ha ändrats. Det här exemplet syftar till att samla in 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://free.blessedness.top/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 kolumner som innehåller de beskrivningar som en lösning innehåller.
| Kolumnetikett | Description |
|---|---|
| Visningsnamn | Namnet på lösningen. |
| Name | 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. |
| Description | Använd denna kolumn om du vill ta med all relevant information om lösningen. |
Här 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 tabellen 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 ställa frågor till tabellen ImportJob gällande importstatusen.
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-tabellen 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 tabellkomponent 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-metoden 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änder du 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. För mer information om hur du mellanlagrar och uppgraderar, gå till 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 utför följande åtgärder:
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 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 valkolumn. Alla attribut som använder det globala valet förhindrar att det globala valet 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);
}
}
}