Zdieľať cez


Práca s riešeniami pomocou Dataverse SDK

Ako súčasť vášho životného cyklu od vývoja až po produkciu môžete chcieť vytvoriť vlastnú automatizáciu na spracovanie určitých úloh. Napríklad v kanáli projektu DevOps možno budete chcieť spustiť vlastný kód alebo skript, ktorý vytvorí izolované prostredie, importuje nespravované riešenie, exportuje toto nespravované riešenie ako spravované riešenie a nakoniec dané prostredie odstráni. Tieto a ďalšie operácie môžete vykonávať pomocou rozhraní API, ktoré máte k dispozícii. Tu je niekoľko príkladov toho, čo môžete dosiahnuť pomocou sady SDK pre .NET a vlastného kódu. Dataverse

Poznámka

Rovnaké operácie môžete vykonať aj pomocou webového rozhrania API. Súvisiace akcie sú: ImportSolution, ExportSolution, CloneAsPatch a CloneAsSolution.

Vzorky kódu v tomto článku používajú typy entít s predčasnou väzbou vygenerované pomocou CrmSvcUtil alebo PAC CLI. Viac informácií: Programovanie s oneskorenou a skorou väzbou pomocou SDK pre .NET

Vytvorenie, export alebo import nespravovaného riešenia

Naučte sa, ako vykonávať niektoré bežné operácie riešenia pomocou kódu C#. Ak si chcete pozrieť kompletnú ukážku funkčného kódu C#, ktorá demonštruje tieto typy operácií s riešeniami a ďalšie, prejdite na stránku Ukážka: Práca s riešeniami .

Vytvorenie vydavateľa

Každé riešenie vyžaduje vydavateľa, ktorého reprezentuje tabuľka Vydavateľ. ... Vydavateľ vyžaduje tieto vlastnosti:

  • Predpona prispôsobenia
  • Jedinečný názov
  • Popisný názov

Poznámka

Pre zdravý prístup k správe životného cyklu aplikácií (ALM) vždy používajte na nasadenie prispôsobení vlastného vydavateľa a riešenie, nie predvolené riešenie a vydavateľa.

Nasledujúca ukážka kódu najprv definuje vydavateľa a potom na základe jedinečného názvu skontroluje, či vydavateľ už existuje. Ak už existuje, predpona prispôsobenia sa mohla zmeniť. Táto vzorka sa snaží zachytiť aktuálny prefix prispôsobenia. Získa aj PublisherId, aby bolo možné záznam vydavateľa odstrániť. Ak sa vydavateľ nenájde, vytvorí sa nový vydavateľ pomocou metódy IOrganizationService.Vytvoriť.

// 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;
}

Vytvorenie nespravovaného riešenia

Keď budete mať k dispozícii vlastného vydavateľa, môžete vytvoriť nespravované riešenie. V nasledujúcej tabuľke sú uvedené stĺpce s popismi, ktoré riešenie obsahuje.

Označenie stĺpca Opis
Zobrazované meno Názov riešenia.
Name Microsoft Dataverse vygeneruje jedinečný názov na základe položky Zobrazovaný názov. Tento jedinečný názov môžete upraviť. Jedinečný názov musí obsahovať iba alfanumerické znaky alebo znak podčiarknutia.
Vydavateľ Na priradenie riešenia k vydavateľovi použite vyhľadávanie Vydavateľ.
Verzia Zadajte verziu vo formáte: hlavná.vedľajšia.zostavenie.revízia, napríklad 1.0.0.0.
Stránka konfigurácie Ak do svojho riešenia zahrniete webový zdroj HTML, pomocou tohto vyhľadávania ho môžete pridať ako označenú konfiguračnú stránku riešenia.
Description Do tohto stĺpca uveďte všetky relevantné podrobnosti o vašom riešení.

Tu je vzorový kód na vytvorenie nespravovaného riešenia, ktoré používa vydavateľa, ktorého sme vytvorili v predchádzajúcej časti.

// 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 vytvorení nespravovaného riešenia pridajte komponenty riešenia ich vytvorením v kontexte tohto riešenia alebo pridaním existujúcich komponentov z iných riešení. Ďalšie informácie: Pridanie novej súčasti riešenia a Pridanie existujúcej súčasti riešenia

Export nespravovaného riešenia

Táto vzorka kódu ukazuje, ako exportovať nespravované riešenie alebo zabaliť spravované riešenie. Tento kód používa triedu ExportSolutionRequest na export komprimovaného súboru, ktoré predstavuje nespravované riešenie. Možnosť vytvorenia spravovaného riešenia sa nastavuje pomocou vlastnosti Spravované. Táto vzorka uloží súbor s názvom samplesolution.zip do výstupného priečinka.

// 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);

Import nespravovaného riešenia

Import (alebo inovácia) riešenia pomocou kódu sa uskutoční pomocou položky ImportSolutionRequest.

// Install or upgrade a solution
byte[] fileBytes = File.ReadAllBytes(ManagedSolutionLocation);

ImportSolutionRequest impSolReq = new ImportSolutionRequest()
{
   CustomizationFile = fileBytes
};

_serviceProxy.Execute(impSolReq);

Sledovanie úspešnosti importu

Tabuľku ImportJob môžete použiť na zachytenie údajov o úspešnosti importu riešenia. Keď zadáte hodnotu ImportJobId pre ImportSolutionRequest, môžete túto hodnotu použiť na dotazovanie tabuľky ImportJob o stave importu. ImportJobId taktiež možno použiť na stiahnutie súboru denníka importu pomocou správy 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);
   }
}

Obsah vlastnosti Data predstavuje reťazec, ktorý reprezentuje súbor XML riešenia.

Pridávanie a odstraňovanie súčastí riešenia

Naučte sa, ako pridávať a odstraňovať súčasti riešenia pomocou kódu.

Pridanie novej súčasti riešenia

Tento príklad ukazuje, ako si môžete vytvoriť súčasť riešenia, ktorá je spojená s konkrétnym riešením. Ak komponent riešenia pri jeho vytvorení nepriradíte ku konkrétnemu riešeniu, bude pridaný iba do predvoleného riešenia a musíte ho do riešenia pridať manuálne alebo pomocou kódu, ktorý je súčasťou dokumentu Pridanie existujúceho komponentu riešenia.

Tento kód vytvorí novú globálnu množinu možností a pridá ju do riešenia s jedinečným názvom, ktorý bude rovnaký ako _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);

Pridať existujúcu súčasť riešenia

Tento príklad ukazuje, ako môžete pridať existujúcu súčasť riešenia do riešenia.

Nasledujúci kód používa ` AddSolutionComponentRequest ` na pridanie ` Account ` ako súčasti riešenia do nespravovaného riešenia.

// 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);

Odstránenie súčasti riešenia

Tento príklad ukazuje, ako môžete odstrániť súčasť riešenia z nespravovaného riešenia. Nasledujúci kód používa ` RemoveSolutionComponentRequest ` na odstránenie komponentu tabuľkového riešenia z nespravovaného riešenia. solution.UniqueName odkazuje na riešenie vytvorené v časti Vytvoriť nespravované riešenie.

// 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);

Odstránenie riešenia

Nasledujúci príklad ukazuje, ako môžete načítať riešenie pomocou riešenia uniquename a následne z výsledkov extrahovať solutionid. V rámci príkladu sa potom použije solutionid s IOrganizationService. Delete metóda na odstránenie riešenia.

// 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);

Klonovanie, oprava a inovácia

Pomocou dostupných rozhraní API môžete vykonať ďalšie operácie s riešením. Pre riešenia klonovania a opravy použite príkazy CloneAsPatchRequest a CloneAsSolutionRequest. Informácie o klonovaní a opravovaní nájdete v časti Vytvorenie opráv riešení.

Pri vykonávaní aktualizácií riešení použite StageAndUpgradeRequest a DeleteAndPromoteRequest. Viac informácií o procese prípravy a aktualizácií nájdete v časti Aktualizácia alebo upgrade riešenia.

Odhaľovanie závislostí riešenia

Tento príklad ukazuje, ako si môžete vytvoriť zostavu, ktorá vám ukáže závislosti medzi súčasťami riešenia.

Tento kód vykonáva tieto operácie:

  • Načíta všetky súčasti riešenia.

  • Načíta všetky závislosti jednotlivých súčastí.

  • Zobrazí zostavu s opisom závislosti pre každú nájdenú závislosť.

// 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);
    }
}

Metóda DependencyReport je v nasledujúcom príklade kódu.

Zostava závislostí

Metóda DependencyReport poskytuje priateľskejšie hlásenie na základe informácií nájdených v rámci závislosti.

Poznámka

V rámci tohto príkladu je metóda implementovaná iba čiastočne. Môže zobraziť hlásenia iba pre súčasti riešení, akými sú atribúty a množiny možností.

/// <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);
}

Zistenie, či je možné odstrániť komponent riešenia

Pomocou hlásenia RetrieveDependenciesForDeleteRequest identifikujte všetky ostatné súčasti riešenia, ktoré by bránili odstráneniu danej súčasti riešenia. Nasledujúca ukážka kódu vyhľadáva atribúty pomocou známeho stĺpca globálnej voľby. Akýkoľvek atribút používajúci globálnu voľbu by zabránil jej vymazaniu.

// 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);
  }
 }
}