Радите са решењима помоћу Dataverse SDK
Као део развоја у животном циклусу производње можда ћете желети да направите прилагођену аутоматизацију за руковање одређеним задацима. На пример, у DevOps каналу пројекта можда ћете желети да извршите неки прилагођени кôд или скрипту која креира sandbox окружење, увози некомплетно решење, извози то некомплетно решење као комплетно и, на крају, брише окружење. Можете да урадите ово и још више користећи API-је који су вам доступни. Следи неколико примера онога што можете да постигнете користећи Dataverse SDK за .NET и прилагођени кôд.
Белешка
Те исте операције можете изводити и помоћу веб API-ја. Сродне радње су: Увези решење, Извези решење, Клонирај као закрпу и Клонирај као решење.
Креирајте, извезите или увезите некомплетно решење
Погледајмо како изводити неке уобичајене операције решења користећи C# кôд. За преглед комплетног радног узорка C# кода који приказује ове врсте операција решења (и више), погледајте чланак Узорак: рад са решењима.
Креирање издавача
Свако решење захтева издавача ког представља Ентитет издавача. Издавач захтева следеће:
- Префикс прилагођавања
- Јединствени назив
- Препознатљиво име
Белешка
За здрав ALM приступ увек користите прилагођени издавач и решење, а не подразумевано решење и издавач за примену прилагођавања.
Следећи узорак кода прво дефинише издавача, а затим проверава да ли издавач већ постоји на основу јединственог имена. Ако већ постоји, префикс прилагођавања је можда промењен, тако да овај узорак жели да ухвати тренутни префикс прилагођавања. PublisherId
се такође евидентира па се запис издавача може избрисати. Ако издавач није пронађен, креира се нови издавач помоћу метода IOrganizationService.Креирај.
// 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 генерише јединствено име засновано на Имену за приказ. Можете да измените јединствено име. Јединствено име мора да садржи само алфанумеричке знакове или знак подвлаке. |
Издавач | Користите проналажење Издавача да бисте повезали решење са издавачем. |
Верзија | Наведите верзију у следећем формату: главна верзија.међуверзија.издање.ревизија (на пример: 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);
}
Након што креирате некомплетно решење, можете да додате компоненте решења тако што ћете их креирати у контексту овог решења или додавањем постојећих компоненти из других решења. Још информација: Додавање нове компоненте решења и Додавање постојеће компоненте решења
Извоз некомплетног решења
Овај узорак кода показује како се извози некопмплетно решење или пакет комплетног решења. Кôд користи класу Захтев за извоз решења за извоз компримоване датотеке која представља некомплетно решење. Опција за креирање комплетног решења се подешава помоћу својства Комплетно. Овај пример чува датотеку под називом 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);
Увоз некомплетног решења
Увоз (или надоградња) решења коришћењем кода врши се помоћу класе Захтев за увоз решења.
// Install or upgrade a solution
byte[] fileBytes = File.ReadAllBytes(ManagedSolutionLocation);
ImportSolutionRequest impSolReq = new ImportSolutionRequest()
{
CustomizationFile = fileBytes
};
_serviceProxy.Execute(impSolReq);
Праћење успеха увоза
Ентитет посла увоза можете користити за снимање података о успеху увоза решења. Када наведете ImportJobId
за Захтев за увоз решења, можете да користите ту вредност да бисте упитали ентитет посла увоза о статусу увоза. ImportJobId
се такође може користити за преузимање датотеке евиденције увоза помоћу поруке Захтев за преузимање форматираних резултата посла увоза.
// 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);
}
}
}