Sdílet prostřednictvím


Práce s řešeními pomocí SDK Dataverse

V rámci vývoje životního cyklu výroby budete možná chtít vytvořit vlastní automatizaci, která bude řešit určité úkoly. Například v kanálu projektu DevOps můžete chtít spustit vlastní kód nebo skript, který vytvoří sandboxové prostředí, importuje nespravované řešení, exportuje toto nespravované řešení jako spravované řešení a nakonec prostředí odstraní. Můžete toho udělat ještě více pomocí API, kterou máte k dispozici. Níže je uvedeno několik příkladů, čeho můžete dosáhnout pomocí Dataverse SDK pro .NET a vlastního kódu.

Poznámka:

Stejné operace můžete také provést pomocí webového rozhraní API. Související akce jsou: ImportSolution, ExportSolution, CloneAsPatch a CloneAsSolution.

Vytvoření, export a import nespravovaného řešení

Podívejme se, jak provádět některé běžné operace řešení pomocí kódu C#. Chcete-li zobrazit kompletní pracovní vzorek kódu C#, který ukazuje nejenom tyto typy operací řešení, viz Ukázka: Práce s řešeními.

Vytvoření vydavatele

Každé řešení vyžaduje vydavatele, kterého zastupuje Vydavatelská entita. Vydavatel vyžaduje následující:

  • Předponu názvů vlastních nastavení
  • Jedinečný název
  • Popisný název

Poznámka:

Pro dobrý přístup ALM používejte při nasazení vlastních úprav vždy vlastní vydavatele a řešení, nikoli výchozí řešení a vydavatele.

Následující ukázka kódu nejprve definuje vydavatele a poté zkontroluje, zda vydavatel již existuje na základě jedinečného názvu. Pokud již existuje, mohla být předpona přizpůsobení změněna, takže se tento vzorek bude snažit zachytit aktuální předponu vlastního nastavení. Údaj PublisherId je také zachycen, takže záznam vydavatele lze vymazat. Pokud vydavatel není nalezen, vytvoří se nový vydavatel pomocí metody 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;
}

Vytvoření nespravovaného řešení

Jakmile bude k dispozici vlastní vydavatel, můžete vytvořit nespravované řešení. V následující tabulce jsou uvedena pole s popisy, které řešení obsahují.

Popisek pole Popis
Zobraz. název Název řešení.
Název Microsoft Dataverse generuje jedinečný název založený na zobrazovaném názvu. Můžete upravit jedinečný název. Jedinečný název musí obsahovat jen alfanumerické znaky nebo podtržítka.
Vydavatel Pomocí vyhledávání vydavatelů přidružte řešení k vydavateli.
Verze Zadejte verzi v následujícím formátu: hlavní_verze.podverze.sestavení.revize (například 1.0.0.0).
Konfigurační stránka Pokud do řešení zahrnete webový prostředek HTML, můžete pomocí tohoto vyhledávání přidat jako určenou konfigurační stránku řešení.
Popis Toto pole použijte k zahrnutí všech relevantních podrobností o vašem řešení.

Níže je ukázkový kód k vytvoření nespravovaného řešení, které používá vydavatele, kterého jsme vytvořili v předchozí části.

// 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 vytvoření nespravovaného řešení můžete přidat komponenty řešení jejich vytvořením v kontextu tohoto řešení nebo přidáním existujících komponent z jiných řešení. Více informací: Přidejte novou součást řešení a Přidejte existující součást řešení

Importujte nespravované řešení

Tato ukázka kódu ukazuje, jak exportovat nespravované řešení nebo balíček spravované řešení. Tento kód používá třídu ExportSolutionRequest k exportu komprimovaného souboru představujícího nespravované řešení. Možnost vytvořit spravované řešení je nastavena pomocí vlastnosti Spravováno. Tento příklad uloží soubor s názvem samplesolution.zip do výstupní složky.

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

Importujte nespravované řešení

Import (nebo upgrade) řešení pomocí kódu je proveden pomocí ImportSolutionRequest.

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

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

_serviceProxy.Execute(impSolReq);

Sledujte úspěšnosti importu

Pomocí entity ImportJob můžete zaznamenat data o úspěchu importu řešení. Když zadáte ImportJobId pro ImportSolutionRequest, můžete tuto hodnotu použít k dotazování entity ImportJob na stav importu. ImportJobId lze také použít ke stažení souboru protokolu importu pomocí RetrieveFormattedImportJobResultsRequest zprávy.

// 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 je řetězec představující soubor XML řešení.

Přidání nebo odebrání součástí řešení

Naučte se, jak přidávat a odebírat komponenty řešení pomocí kódu.

Přidání nové součásti řešení

Tento vzorek ukazuje, jak vytvořit komponentu řešení, která je spojena s konkrétním řešením. Pokud při vytvoření nepřipojíte komponentu řešení ke konkrétnímu řešení, bude přidáno pouze k výchozímu řešení a budete je muset přidat do řešení ručně nebo pomocí kódu obsaženého v Přidejte existující komponentu řešení.

Tento kód vytvoří novou globální sadu možností a přidá ji do řešení s jedinečným názvem _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);

Přidání existující součásti řešení

Tento příklad ukazuje, jak přidat existující komponentu řešení k řešení.

Následující kód používá AddSolutionComponentRequest pro přidání entity Account jako součást řešení do nespravovaného řešení.

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

Odebrat součást řešení

Tento příklad ukazuje, jak odebrat komponentu řešení z nespravovaného řešení. Následující kód používá RemoveSolutionComponentRequest pro odebrání součásti řešení entity z nespravovaného řešení. Odkazy solution.UniqueName na řešení vytvořené v Vytvořte nespravované řešení.

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

Odstranění řešení

Následující příklad ukazuje, jak načíst řešení pomocí uniquename řešení a poté extrahovat solutionid z výsledků. Ukázka poté použije solutionid s IOrganizationService. Metoda Delete k odstranění řešení.

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

Klonování, opravy a aktualizace

Pomocí dostupných rozhraní API můžete provádět další operace řešení. Pro řešení klonování a opravování použijte CloneAsPatchRequest a CloneAsSolutionRequest. Pro více informací o klonování a opravě viz Vytvoření oprav řešení.

Při provádění upgradů řešení použijte StageAndUpgradeRequest a DeleteAndPromoteRequest. Další informace o procesu postupování a upgradů naleznete na stránce Upgradujte nebo aktualizujte řešení.

Zjištění závislostí řešení

Tento příklad ukazuje, jak vytvořit sestavu znázorňující závislosti mezi komponentami řešení.

Tento kód:

  • Načtěte všechny komponenty pro řešení.

  • Načte všechny závislosti pro každou součást.

  • Pro každou nalezenou závislost zobrazí zprávu popisující závislost.

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

Metoda DependencyReport je v následující ukázce kódu.

Sestava závislostí

Metoda DependencyReport poskytuje popisnější zprávu na základě informací nalezených v závislosti.

Poznámka:

V tomto vzorku je metoda implementována pouze částečně. Může zobrazovat zprávy pouze pro komponenty atributů a sadu 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);
}

Zjistit, zda lze součást řešení odstranit

Použijte zprávu RetrieveDependenciesForDeleteRequest, která identifikuje jakékoli další komponenty řešení, které by zabránily odstranění dané komponenty řešení. Následující ukázka kódu hledá všechny atributy pomocí známé globální sady voleb. Jakýkoli atribut používající globální sadu voleb by zabránil vymazání globální sady voleb.

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