Práca s riešeniami pomocou Dataverse SDK

V rámci vývojového životného cyklu produktov si možno budete chcieť vytvoriť vlastnú automatizáciu na vykonávanie 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. Toto a ešte viac môžete urobiť pomocou rozhraní API, ktoré máte k dispozícii. Nižšie uvádzame niekoľko príkladov toho, čo môžete dosiahnuť pomocou rozhraní Dataverse SDK pre .NET a vlastného kódu..

Poznámka

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

Vytvorenie, export alebo import nespravovaného riešenia

Pozrime sa na to, ako možno vykonať niektoré bežné operácie s riešeniami pomocou kódu C#. Ak chcete zobraziť úplnú pracovnú vzorku kódu C#, ktorá demonštruje tieto typy operácií s riešeniami (a ďalšie), pozrite si časť Ukážka: Práca s riešeniami.

Vytvorenie vydavateľa

Každé riešenie si vyžaduje vydavateľa, ktorého zastupuje Entita vydavateľa. Vydavateľ si vyžaduje nasledovné:

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

Poznámka

V záujme správneho prístupu ALM na nasadzovanie prispôsobení vždy používajte vlastného vydavateľa a riešenie, nie predvolené riešenie a vydavateľa.

Nasledujúca vzorka kódu najskôr definuje vydavateľa a na základe jedinečného názvu následne skontroluje, či vydavateľ už existuje. Ak už existuje, predpona prispôsobenia sa mohla zmeniť, takže táto vzorka sa snaží získať aktuálnu predponu 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é polia s popismi, ktoré riešenie obsahuje.

Označenie poľa Opis
Názov zobrazenia Názov riešenia.
Meno 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 v nasledujúcom formáte: hlavná.vedľajšia.zostava.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.
Popis V tomto poli môžete uviesť všetky relevantné podrobnosti o vašom riešení.

Nižšie je uvedený ukážkový kód na vytvorenie nespravovaného riešenia, ktoré používa vydavateľa, ktorého sme vytvorili v predchádzajúcej sekcii.

// 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 môžete súčasti riešenia pridať tak, že ich vytvoríte v kontexte tohto riešenia alebo že pridáte existujúce súčasti 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

Entitu ImportJob môžete použiť na zber údajov o úspechu importu riešenia. Keď zadáte ImportJobId pre ImportSolutionRequest, môžete použiť túto hodnotu na dotazovanie stavu importu u entity ImportJob. 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 súčasť riešenia nepriradíte ku konkrétnemu riešeniu, ktoré sa vytvorí, pridá sa iba k predvolenému riešeniu a budete ho musieť pridať do riešenia manuálne alebo pomocou kódu v sekcii Pridať existujúcu súčasť 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 entity Account ako súčasť riešenia pre nespravované riešenie.

// 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 súčasti riešenia entity 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. Metóda Delete 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 klonovacie a opravné riešenia použite 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. Ďalšie informácie o procese fázovania a inováciách nájdete v sekcii Inovácia alebo aktualizácia 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:

  • 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 compoent 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);
}

Zistite, či možno odstrániť súčasť 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úci príklad kódu hľadá všetky atribúty pomocou známej globálnej množiny možností. Akýkoľvek atribút, ktorý využíva globálnu množinu možností, by zabránil odstráneniu globálnej množiny možností.

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