Работа с решения с помощта на Dataverse SDK
Като част от вашето развитие към жизнения цикъл на производството може да искате да създадете персонализирана автоматизация за справяне с определени задачи. Например, във вашия тръбопровод за проекти на DevOps може да искате да изпълните някакъв персонализиран код или скрипт, който създава среда за пясъчна кутия, импортира неуправляемо решение, експортира това неуправляемо решение като завършено решение и накрая изтрива средата. Можете да направите това и още повече, като използвате достъпните за вас API. По-долу са някои примери за това, което можете да постигнете с помощта на Dataverse SDK for .NET, крайната точка на персонализиран код.
Бележка
Можете също да извършвате същите тези операции, използвайки Web API. Свързаните действия са: ImportSolution, ExportSolution, CloneAsPatch и CloneAsSolution.
Създаване, експортиране или импортиране на незавършено решение
Нека да видим как да извършим някои общи операции с решение, като използваме C# код. За да видите пълната работна проба на C# код, която демонстрира тези видове операции с решение (и повече), вижте Пример: Работа с решения.
Създаване на публикуващ
Всяко решение изисква издател, представен от обекта „Издател“. Издателят изисква следното:
- Префикс на персонализиране
- Еднозначно име
- Удобно име
Бележка
За здравословен подход на ALM винаги използвайте персонализиран издател и решение, а не решение по подразбиране и издател за разполагане на вашите персонализиране.
Следващата проба от код първо определя издател и след това проверява дали издателят вече съществува въз основа на уникалното име. Ако той вече съществува, префиксът за персонализиране може да е променен, така че тази проба се стреми да улови текущия префикс за персонализиране. PublisherId
се регистрира така, че записът на издателя да може да бъде изтрит. Ако издателят не бъде намерен, се създава нов издател с помощта на метода 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;
}
Създаване на незавършено решение
След като разполагате с персонализиран издател, можете да създадете неуправлявано решение. Следващата таблица изброява полетата с описания, които съдържа решение.
Етикет на полето | Описание |
---|---|
Показвано име | Името за решението. |
Име | Microsoft Dataverse генерира уникално име на базата на Показвано име. Можете да редактирате еднозначното име. Еднозначното име трябва да съдържа само буквено-цифрени знаци или знака за долна черта. |
Издател | Използвайте справката за Издател за свързване на решението с издател. |
Версия | Укажете версия, като използвате следния формат: major.minor.build.revision (например, 1.0.0.0. |
Страница на конфигуриране | Ако включите HTML уеб ресурс в решението си, можете да използвате това търсене, за да го добавите като определена конфигурационна страница на решение. |
Описание | Използвайте това поле, за да включите всички важни подробности относно вашето решение. |
По-долу е примерен код за създаване на неуправлявано решение, което използва издателя, който създадохме в предишния раздел.
// 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);
}
След като създадете неуправляемо решение, можете да добавите компоненти на решение, като ги създадете в контекста на това решение или като добавите съществуващи компоненти от други решения. Повече информация: Добавете нов компонент на решение и Добавете съществуващ компонент на решение
Експортиране на неуправлявано решение
Тази проба от кодове показва как да експортирате неуправляемо решение или да пакетирате завършено решение. Кодът използва класа ExportSolutionRequest за експортиране на компресиран файл, представляващ незавършено решение. Опцията за създаване на завършено решение се задава чрез използване на свойството Завършено. Тази проба запазва файл с име samplesolution.zip към изходната папка.
// 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);
Импортиране на неуправлявано решение
Импортирането (или надграждането) на решение с помощта на код се осъществява чрез ImportSolutionRequest.
// Install or upgrade a solution
byte[] fileBytes = File.ReadAllBytes(ManagedSolutionLocation);
ImportSolutionRequest impSolReq = new ImportSolutionRequest()
{
CustomizationFile = fileBytes
};
_serviceProxy.Execute(impSolReq);
Проследяване на успеха при импортиране
Можете да използвате обекта ImportJob за улавяне на данни за успеха на импортирането на решение. Когато посочите ImportJobId
за ImportSolutionRequest, можете да използвате тази стойност, за да питате обекта ImportJob относно състоянието на импортирането. ImportJobId
също може да се използва за изтегляне на файл от входния файл с помощта на съобщението 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);
}
}
Съдържанието на свойството Data
е низ, представляващ XML файла на решението.
Добавяне и премахване на компоненти на решение
Научете как да добавяте и премахвате компоненти на решение с помощта на код.
Добавяне на нов компонент на решение
Тази извадка показва как да създадете компонент на решение, който е свързан с конкретно решение. Ако не свържете компонента на решение с конкретно решение, когато той е създаден, той ще бъде добавен само към подразбиращото се решение и ще трябва да го добавите ръчно или с помощта на кода, включен в Добавете съществуващ компонент на решение.
Този код създава нов глобален набор от опции и го добавя към решението с уникално име, равно на _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);
Добавяне на съществуващ компонент на решение
Този пример показва как да добавите съществуващ компонент на решение към решение.
Следният код използва AddSolutionComponentRequest, за да добавите обекта Account
като компонент на решение към неуправлявано решение.
// 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);
Премахване на компонент на решение
Този пример показва как да премахнете компонент на решение от неуправлявано решение. Следният код използва RemoveSolutionComponentRequest, за да премахнете компонента на решение на обект от неуправлявано решение. solution.UniqueName
препраща към решението, създадено в Създайте неуправляемо решение.
// 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);
Изтриване на решение
Следващата извадка показва как да извлечете решение с помощта на uniquename
на решението и след това извлечете solutionid
от резултатите. След това примерът използва solutionid
с IOrganizationService. Delete метод за изтриване на решението.
// 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);
Клониране, корекция и надграждане
Можете да извършвате допълнителни операции за решение, като използвате наличните API. За решения за клониране и корекции използвайте CloneAsPatchRequest и CloneAsSolutionRequest. За информация относно клонирането и корекциите вижте Създайте корекции за решение.
Когато извършвате надстройки на решения, използвайте StageAndUpgradeRequest и DeleteAndPromoteRequest. За повече информация относно процеса на създаване на етапи и надстройки вижте Надстройте или актуализирайте решение.
Откриване на зависимости на решение
Тази извадка показва как да създадете отчет, показващ зависимостите между компонентите на решението.
Този код ще:
Извлече всички компоненти за решение.
Извлече всички зависимости за всеки компонент.
За всяка намерена зависимост се показва отчет, описващ зависимостта.
// 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
е в следната кодова извадка.
Отчет на зависимостта
Методът DependencyReport
предоставя по-приятелско съобщение въз основа на информация, открита в зависимост.
Бележка
В тази извадка методът се прилага само частично. Той може да показва съобщения само за компоненти на атрибути и опции за решение.
/// <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);
}
Откриване дали компонент на решението може да бъде изтрит
Използвайте съобщението RetrieveDependenciesForDeleteRequest за идентифициране на всички други компоненти на решение, което би попречило даден компонент на решение да бъде изтрит. Следващата извадка от кодове търси всички атрибути, използващи известен глобален набор от опции. Всеки атрибут, използващ глобалния набор от опции, ще попречи на глобалния набор от опции да бъде изтрит.
// 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);
}
}
}