Поділитися через


Робота з рішеннями за допомогою Dataverse SDK

У рамках життєвого циклу від розробки до продакшну вам може знадобитися створити власну автоматизацію для виконання певних завдань. Наприклад, у воронці проекту DevOps можна виконати певний настроюваний код або сценарій, який створює ізольоване середовище, імпортує некероване рішення, експортує це некероване рішення як кероване рішення і, нарешті, видаляє середовище. Ви можете виконувати ці та інші операції за допомогою доступних вам API. Ось кілька прикладів того, чого можна досягти за допомогою Dataverse SDK для .NET і користувацького коду.

Нотатка

Крім того, ці операції можна виконати за допомогою Web API. Пов'язані дії: імпорт рішення ImportSolution, експорт рішення ExportSolution, клонувати як виправлення CloneAsPatch і клонувати як рішення CloneAsSolution.

Приклади коду в цій статті використовують ранньо зв'язані типи сутностей, згенеровані за допомогою CrmSvcUtil або PAC CLI. Додаткові відомості: Програмування з пізньою та ранньою прив'язкою з використанням SDK для .NET

Створення, експорт або імпорт некерованого рішення

Дізнайтеся, як виконувати деякі поширені операції розв’язання за допомогою коду C#. Щоб переглянути повний робочий зразок коду C#, який демонструє ці типи операцій з рішеннями та багато іншого, перейдіть до розділу Зразок: Робота з рішеннями.

Створення видавця

Для кожного рішення потрібен видавець, представлений таблицею Publisher. Видавець повинен мати такі властивості:

  • Префікс настроювання
  • Унікальне ім’я
  • Зрозуміле ім’я

Нотатка

Для здорового підходу до керування життєвим циклом програми (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 створює унікальне ім’я на основі Короткого імені. Унікальне ім'я можна змінити. Унікальне ім’я повинно містити лише букви, цифри або символ підкреслення.
Видавець Скористайтесь підстановкою Видавця, щоб зв’язати рішення з видавцем.
Версія Вкажіть версію, використовуючи format: 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 a для 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);
  }
 }
}