Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
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żesz wykonać te operacje i nie tylko za pomocą interfejsów API, które masz do dyspozycji. 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.
Przykłady kodu w tym artykule korzystają z typów encji z wczesnym powiązaniem, wygenerowanych przy użyciu narzędzia CrmSvcUtil lub interfejsu wiersza polecenia PAC. Więcej informacji: Programowanie z późnym i wczesnym wiązaniem przy użyciu zestawu SDK dla platformy .NET
Tworzenie, eksportowanie lub importowanie rozwiązania niezarządzanego
Dowiedz się, 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.
Utwórz wydawcę
Każde rozwiązanie wymaga wydawcy, reprezentowanego przez tabelę wydawca. Wydawca wymaga następujących właściwości:
- Prefiks dostosowywania
- Unikatowa nazwa
- Przyjazna nazwa
Uwaga
Aby zapewnić prawidłowe zarządzanie cyklem życia aplikacji (ALM), podczas wdrażania własnych dostosowań zawsze używaj niestandardowego wydawcy i rozwiązania, a nie domyślnego wydawcy i rozwiązania.
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 prefiks dostosowywania już istnieje, mógł zostać zmieniony. Ten przykład ma na celu przechwycenie aktualnego 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 znaleziony, zostanie utworzony nowy wydawca za pomocą metody IOrganizationService.Create.
// 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;
}
Tworzenie rozwiązania niezarządzanego
Gdy wydawca niestandardowy jest już dostępny, można następnie utworzyć rozwiązanie niezarządzane. W poniższej tabeli zamieszczono listę kolumn zawierających opisy zawarte w rozwiązaniu.
| Etykieta kolumny | Podpis |
|---|---|
| Nazwa wyświetlana | Nazwa rozwiązania. |
| Nazwa/nazwisko | Microsoft Dataverse generuje unikalną nazwę na podstawie elementu Nazwa wyświetlana. Unikatową nazwę można edytować. Unikalna nazwa musi zawierać wyłącznie znaki alfanumeryczne lub znak podkreślenia. |
| Wydawca | Użyj funkcji wyszukiwanie Wydawcy,aby skojarzyć rozwiązanie z wydawcą. |
| Wersja | Określ wersję w formacie: główna.podrzędna.kompilacja.rewizja, 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 tej kolumny, 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
Eksportowanie rozwiązania niezarządzanego
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 rozwiązania niezarządzanego
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);
Śledzenie sukcesu importu
Tabela ImportJob może służyć do przechwytywania danych o powodzeniu importu rozwiązania. Jeśli dla żądania ImportSolutionRequest określisz wartość ImportJobId, możesz użyć tej wartości, aby wysłać zapytanie do tabeli ImportJob o stan importu.
ImportJobId może być również używany do pobierania pliku dziennika importu za pomocą komunikatu 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.
Dodawanie i usuwanie składników rozwiązania
Informacje o dodawaniu i usuwaniu składników rozwiązania przy użyciu kodu.
Dodawanie nowego składnika do rozwiązania
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);
Dodaj istniejący składnik rozwiązania
W tym przykładzie pokazano, jak dodać istniejący składnik rozwiązania do rozwiązania.
Poniższy kod używa elementu AddSolutionComponentRequest, aby dodać tabelę Account jako składnik rozwiązania w rozwiązaniu niezarządzanym.
// 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);
Użyj AddSolutionComponentRequest tylko wtedy, gdy znasz poprawną ComponentType wartość całkowitą dla składnika, który chcesz dodać. Większość typowych typów składników jest definiowana w wybranej skali globalnej componenttype, która jest używana przez kolumnę wyboru SolutionComponent.ComponentType, ale niektóre nowsze typy składników mogą nie być tam wymienione. Jeśli to możliwe, utwórz lub zaktualizuj składnik w kontekście rozwiązania docelowego przy użyciu SolutionUniqueName właściwości lub parametru opcjonalnego SolutionUniqueName . Więcej informacji: Kojarzenie składnika rozwiązania z rozwiązaniem przy użyciu opcjonalnego parametru.
Usuwanie składnika rozwiązania
W tym przykładzie pokazano, jak usunąć składnik rozwiązania z rozwiązania niezarządzanego. Poniższy kod używa elementu RemoveSolutionComponentRequest, aby usunąć składnik rozwiązania tabeli 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);
Usuń rozwiązanie
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. Przykład używa elementu solutionid wraz zIOrganizationService.
Delete metoda usuwania rozwiązania.
// 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);
Klonowanie, poprawianie i uaktualnianie
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.
Wykrywanie zależności rozwiązań
W tym przykładzie przedstawiono sposób tworzenia raportu pokazującego zależności między składnikami rozwiązania.
Ten kod wykonuje następujące operacje:
Pobierz wszystkie składniki rozwiązania.
Pobierz 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.
Raport zależności
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. Może wyświetlać komunikaty tylko dla składników rozwiązania typu atrybut i zestaw opcji.
/// <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);
}
Wykrywa czy składniki rozwiązania można usunąć
Użyj komunikatu RetrieveDependenciesForDeleteRequest, aby zidentyfikować wszelkie inne składniki rozwiązania, które uniemożliwiłyby usunięcie danego składnika rozwiązania. Poniższy przykładowy kod wyszukuje wszystkie atrybuty korzystające ze znanej globalnej kolumny wyboru. Każdy atrybut korzystający z wyboru globalnego uniemożliwia usunięcie tego wyboru globalnego.
// 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);
}
}
}