Praca z rozwiązaniami przy użyciu interfejsów API Dataverse SDK
W ramach opracowywania cyklu życia aplikacji można utworzyć automatyzację niestandardową w celu obsługi określonych zadań. Na przykład w potoku projektu DevOps może zaistnieć konieczność wykonania niestandardowego kodu lub skryptu tworzącego środowisko piaskownicy, zaimportowania rozwiązania niezarządzanego, wyeksportowania rozwiązania niezarządzanego jako rozwiązanie zarządzane i ostatecznie usunięcia środowiska. Można to zrobić i wykonać, korzystając z dostępnych interfejsów API. Poniżej przedstawiamy kilka przykładów tego, co możesz osiągnąć, używając Dataverse SDK dla .NET i własnego kodu.
Uwaga
Te same operacje można również wykonać przy użyciu interfejsu API sieci Web. Odpowiednie akcje to: ImportSolution, ExportSolution, CloneAsPatch i CloneAsSolution.
Zobaczmy, jak wykonywać typowe operacje na rozwiązaniach za pomocą kodu C#. Aby wyświetlić pełną próbkę kodu w języku C#, która prezentuje te typy operacji rozwiązania (i nie tylko), zobacz przykład: Praca z rozwiązaniami.
Każde rozwiązanie wymaga wydawcy, reprezentowanego przez encję wydawca. Wydawca wymaga następujących czynności:
- Prefiks nazwy dostosowywania
- Unikatowa nazwa
- Przyjazna nazwa
Uwaga
W celu poprawnego ALM do wdrażania dostosowań zawsze używaj niestandardowego wydawcy i rozwiązania, a nie rozwiązania domyślnego i wydawcy.
Poniższa próbka kodu definiuje w pierwszej kolejności wydawcę, a następnie sprawdza, czy wydawca już istnieje na podstawie unikatowej nazwy. Jeśli już istnieje, może to oznaczać, że prefiks dostosowań się zmienił, więc ten przykład próbuje uchwycić bieżący prefiksu dostosowywania. Element PublisherId
jest również uchwycony w taki sposób, aby można było usunąć rekord wydawcy. Jeśli wydawca nie zostanie odnaleziony, nowy wydawca zostanie utworzony przy użyciu metody IOrganizationService. Metody Utwórz.
// 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;
}
Po udostępnieniu niestandardowego wydawcy można utworzyć rozwiązanie niezarządzane. W poniższej tabeli zamieszczono listę pól zawierających opisy zawarte w rozwiązaniu.
Etykieta pola | Opis |
---|---|
Wyświetlana nazwa | Nazwa rozwiązania. |
Nazwisko | Microsoft Dataverse generuje unikatową nazwę na podstawie Wyświetlanej nazwy. Unikatową nazwę można edytować. Nazwa może zawierać tylko znaki alfanumeryczne i znak podkreślnik. |
Wydawca | Użyj funkcji wyszukiwanie Wydawcy,aby skojarzyć rozwiązanie z wydawcą. |
Wersja | Określ wersję przy użyciu następującego formatu: główna.pomocnicza.kompilacja.poprawka (na przykład 1.0.0.0. |
Strona konfiguracji | Jeśli w rozwiązaniu dołączono zasób sieci Web w formacie HTML, można użyć tego wyszukiwania, aby dodać je jako wyznaczoną stronę konfiguracyjną rozwiązania. |
Opis | Użyj tego pola, aby podać wszystkie istotne informacje o rozwiązaniu. |
Poniżej przedstawiono przykładowy kod do utworzenia rozwiązania niezarządzanego, w którym jest używany wydawca utworzony w poprzedniej sekcji.
// 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);
}
Po utworzeniu rozwiązania niezarządzanego można dodać składniki rozwiązania, tworząc je w kontekście tego rozwiązania lub dodając istniejące składniki z innych rozwiązań. Więcej informacji: Dodawanie nowego składnika rozwiązania i Dodawanie istniejącego składnika rozwiązania
Ten przykładowy kod ilustruje sposób eksportowania rozwiązania niezarządzanego lub pakowania rozwiązania zarządzanego. W kodzie jest używana klasa ExportSolutionRequest w celu wyeksportowania skompresowanego pliku reprezentującego rozwiązanie niezarządzane. Opcja tworzenia rozwiązania zarządzanego jest ustawiana przy użyciu właściwości zarządzanej. W tym przykładzie zapisywany jest plik o nazwie samplesolution.zip w folderze wyjściowym.
// 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);
Importowanie (lub uaktualnianie) rozwiązania przy użyciu kodu jest realizowane za pomocą ImportSolutionRequest.
// Install or upgrade a solution
byte[] fileBytes = File.ReadAllBytes(ManagedSolutionLocation);
ImportSolutionRequest impSolReq = new ImportSolutionRequest()
{
CustomizationFile = fileBytes
};
_serviceProxy.Execute(impSolReq);
Encja ImportJob może służyć do przechwytywania danych o powodzeniu importu rozwiązania. Jeśli ImportJobId
zostanie określona dla ImportSolutionRequest, można jej użyć w celu wykonania zapytania do encji ImportJob dotyczącej stanu importu. ImportJobId
można również użyć do pobrania pliku importu dziennika za pomocą 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);
}
}
Zawartość właściwości Data
jest ciągiem reprezentującym plik XML rozwiązania.
Informacje o dodawaniu i usuwaniu składników rozwiązania przy użyciu kodu.
W tym przykładzie pokazano, jak utworzyć składnik rozwiązania skojarzony z określonym rozwiązaniem. Jeśli składnik rozwiązania nie zostanie skojarzony z określonym rozwiązaniem podczas jego tworzenia, zostanie dodany do rozwiązania domyślnego i będzie konieczne ręczne dodanie go do rozwiązania lub użycie kodu zawartego w polu Dodaj istniejący składnik rozwiązania.
Ten kod powoduje utworzenie nowego globalnego zestaw opcji i dodanie go do rozwiązania o unikatowej nazwie równej _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);
W tym przykładzie pokazano, jak dodać istniejący składnik rozwiązania do rozwiązania.
W poniższym kodzie zastosowano AddSolutionComponentRequest aby dodać encję Account
jako składnik rozwiązania do rozwiązania niezarządzanego.
// 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);
W tym przykładzie pokazano, jak usunąć istniejący składnik rozwiązania niezarządzanego. W poniższym kodzie zastosowano RemoveSolutionComponentRequest aby usunąć składnik rozwiązania z rozwiązania niezarządzanego. solution.UniqueName
zawiera odwołanie do rozwiązania utworzonego w ramach tworzenia rozwiązania niezarządzanego.
// 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);
W poniższym przykładzie pokazano, jak pobrać rozwiązanie przy użyciu rozwiązania uniquename
, a następnie wyodrębnić solutionid
spośród wyników. Próbka korzysta z solutionid
wraz z IOrganizationService. Delete , aby usunąć rozwiązanie.
// 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);
Korzystając z dostępnych interfejsów API, można wykonywać dodatkowe operacje na rozwiązaniach. Do klonowania i poprawiania rozwiązań są używane CloneAsPatchRequest i CloneAsSolutionRequest. Aby uzyskać informacje na temat klonowania i poprawiania, zobacz Tworzenie poprawek rozwiązań.
Podczas uaktualniania rozwiązania korzystaj z StageAndUpgradeRequest i DeleteAndPromoteRequest. Więcej informacji na temat procesu przemieszczania i uaktualniania można znaleźć w artykule uaktualnianie i aktualizowanie rozwiązania.
W tym przykładzie przedstawiono sposób tworzenia raportu pokazującego zależności między składnikami rozwiązania.
Ten kod:
Pobierze wszystkie składniki rozwiązania.
Pobierze wszystkie zależności dla każdego składnika.
Dla każdej znalezionej zależności wyświetli raport z opisem zależności.
// 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);
}
}
Metoda DependencyReport
znajduje się w następującym przykładzie kodu.
Metoda DependencyReport
dostarcza bardziej przyjazne komunikaty na podstawie informacji zawartych w zależności.
Uwaga
W tym przykładzie metoda jest tylko częściowo zaimplementowana. Umożliwia wyświetlanie komunikatów tylko dla składników rozwiązań zestawu opcji i atrybutów.
/// <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);
}
W celu zidentyfikowania innych rozwiązania, które uniemożliwiają usunięcie określonego składnika rozwiązania należy użyć komunikatu RetrieveDependenciesForDeleteRequest. Poniższe przykładowe kody wyszukują dowolne atrybuty przy użyciu znanego globalnego zestawu opcji. Każdy atrybut wykorzystujący globalny zestaw opcji uniemożliwi usunięcie globalnego elementu zestawu opcji.
// 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);
}
}
}