Работа с решения с помощта на Dataverse SDK

Като част от жизнения цикъл на вашето развитие до производство, може да искате да създадете персонализирана автоматизация, за да се справите с определени задачи. Например, във вашия тръбопровод за проекти на DevOps може да искате да изпълните някакъв персонализиран код или скрипт, който създава среда за пясъчна кутия, импортира неуправляемо решение, експортира това неуправляемо решение като завършено решение и накрая изтрива средата. Можете да извършвате тези и други операции, като използвате API, които са достъпни за вас. Ето няколко примера за това, което можете да постигнете с помощта на SDK за .NET Dataverse и персонализиран код.

Бележка

Можете също да извършвате същите тези операции, използвайки 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 генерира уникално име на базата на Показвано име. Можете да редактирате еднозначното име. Еднозначното име трябва да съдържа само буквено-цифрени знаци или знака за долна черта.
Издател Използвайте справката за Издател за свързване на решението с издател.
Версия Посочете версия, като използвате формата: 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, за да събирате данни за успеха на импортирането на решението. Когато зададете an 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);

Използвайте AddSolutionComponentRequest само когато знаете правилната ComponentType целочислена стойност за компонента, който искате да добавите. Повечето често срещани типове компоненти са дефинирани в глобалния componenttype избор, който се използва от колоната SolutionComponent.ComponentType избор, но някои по-нови типове компоненти може да не са посочени там. Когато е възможно, създайте или обновете компонента в контекста на целевото решение, като използвате свойство SolutionUniqueName или опционален SolutionUniqueName параметър. Повече информация: Асоциирайте компонент на решение с решение чрез опционален параметър.

Премахване на компонент на решение

Този пример показва как да премахнете компонент на решение от неуправлявано решение. Следващият код използва компонента 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);
  }
 }
}