Partajați prin


Lucrul cu soluții care utilizează SDK Dataverse

Ca parte a dezvoltării dvs. pentru ciclul de viață al producției, este posibil să doriți să creați automatizare particularizată pentru a face față anumitor sarcini. De exemplu, în canalul de proiect DevOps, s-ar putea să doriți să executați un cod sau script particularizat care creează un mediu de tip sandbox, importă o soluție negestionată, exportă soluția care negestionată ca soluție gestionată și, în cele din urmă, șterge mediul. Puteți face acest lucru și multe altele folosind API-urile care vă sunt disponibile. Mai jos sunt câteva exemple despre ceea ce puteți realiza folosind SDK pentru .NET Dataverse și codul particularizat.

Notă

Puteți efectua, de asemenea, aceleași operațiuni utilizând API Web. Acțiunile conexe sunt: ImportSolution, ExportSolution, CloneAsPatch și CloneAsSolution.

Creați, exportați sau importați o soluție negestionată

Să vedem cum să efectuați unele operațiuni de soluție comune folosind codul C #. Pentru a vizualiza eșantionul de cod de lucru complet C# care demonstrează aceste tipuri de operațiuni de soluție (și mai multe), consultați Eșantion: Lucrul cu soluții.

Creați un editor

Fiecare soluție necesită un editor, reprezentat de Entitatea Editor. Un editor necesită următoarele:

  • Un prefix particularizare
  • Un nume unic
  • Un nume prietenos

Notă

Pentru o abordare ALM corespunzătoare, utilizați întotdeauna un editor și o soluție particularizate, nu soluția și editorul implicite pentru implementarea particularizărilor.

Următorul exemplu de cod definește mai întâi un editor și apoi verifică pentru a vedea dacă acesta există deja, pe baza numelui unic. Dacă există deja, este posibil ca prefixul de particularizare să fi fost schimbat, astfel că acest exemplu încearcă să surprindă prefixul de particularizare actual. PublisherId este de asemenea capturat astfel încât înregistrarea editorului să poată fi ștearsă. Dacă editorul nu este găsit, un editor nou este creat folosind metoda IOrganizationService.Creare .

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

Crearea unei soluții negestionate

După ce aveți un editor particularizat disponibil, puteți crea apoi o soluție negestionată. Următorul tabel listează câmpurile cu descrieri pe care le conține o soluție.

Etichetă câmp Descriere
Nume afișat Numele soluției.
Nume Microsoft Dataverse generează un nume unic bazat pe Numele afișat. Aveți posibilitatea să editați numele unic.. Numele unic trebuie să conțină numai caractere alfanumerice sau caracterul de subliniere.
Editor Folosește căutarea Editor pentru a asocia soluția cu un editor.
Versiune Specificați o versiune folosind următorul format: major.minor.generare.revizuire (de exemplu, 1.0.0.0.
Pagină de configurare Dacă includeți o resursă Web HTML în soluția dvs., puteți utiliza această căutare pentru a o adăuga ca pagina de configurare soluție desemnată.
Descriere Utilizați acest câmp pentru a include orice detalii relevante despre soluția dvs.

Mai jos este un exemplu de cod pentru a crea o soluție negestionată care folosește editorul pe care l-am creat în secțiunea anterioară.

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

După ce creați o soluție negestionată, puteți adăuga componente ale soluției creându-le în contextul acestei soluții sau adăugând componente existente din alte soluții. Mai multe informații: Adăugați o componentă nouă de soluțieși Adăugați o componentă de soluție existentă

Exportați o soluție negestionată

Acest eșantion de cod arată cum se poate exporta o soluție negestionată sau realiza ca pachet o soluție gestionată. Codul folosește clasa ExportSolutionRequest pentru a exporta un fișier comprimat reprezentând o soluție negestionată. Opțiunea de a crea o soluție gestionată este setată folosind proprietatea Gestionat. Acest exemplu salvează un fișier numit samplesolution.zip la folderul de ieșire.

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

Importul unei soluții negestionate

Importul (sau upgradarea) unei soluții prin utilizarea codului se realizează cu Import Solicitare Soluție.

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

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

_serviceProxy.Execute(impSolReq);

Urmărirea succesului la import

Puteți utiliza entitatea ImportJob pentru a capta date despre succesul importului soluției. Când specificați un ImportJobId pentru Import Solicitare Soluție, puteți utiliza această valoare pentru a interoga entitatea ImportJob despre starea importului. ImportJobId poate fi, de asemenea, utilizat pentru a descărca un fișier jurnal de import folosind mesajul 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);
   }
}

Conținutul proprietății Data este un șir care reprezintă fișierul XML al soluției.

Adăugarea sau eliminarea componentelor soluției

Aflați cum puteți adăuga și elimina componentele soluției folosind codul.

Adăugarea unei noi componente de soluție

Acest eșantion arată cum puteți crea o componentă de soluție care este asociată cu o soluție specifică. Dacă nu asociați componenta soluției cu o soluție specifică atunci când este creată, aceasta va fi adăugată numai la soluția implicită și va trebui să o adăugați manual la o soluție sau folosind codul inclus în Adăugați o componentă de soluție existentă.

Acest cod creează un nou set de opțiuni global și îl adaugă la soluție cu un nume unic egal cu _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);

Adăugarea unei componente de soluție existente

Acest eșantion arată cum se adaugă o componentă de soluție existentă la o soluție.

Următorul cod utilizează AddSolutionComponentRequestpentru a adăuga entitatea Account ca o componentă de soluție la o soluție negestionată.

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

Eliminarea unei componente a soluției

Acest eșantion arată cum se elimină o componentă de soluție dintr-o soluție negestionată. Următorul cod utilizează RemoveSolutionComponentRequest pentru a elimina o componentă de soluție entitate de la o soluție negestionată. solution.UniqueName face referire la soluția creată în Creați o soluție negestionată.

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

Ştergerea unei soluții

Următorul eșantion arată cum să regăsiți o soluție folosind soluția uniquename apoi să extrageți solutionid din rezultate. Eșantionul utilizează apoi solutionid cuIOrganizationService. Metoda Delete pentru ștergerea soluției.

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

Clonare, corectare și upgrade

Puteți efectua operațiuni suplimentare de soluție utilizând API-urile disponibile. Pentru soluții de clonare și de aplicare, utilizați CloneAsPatchRequest și CloneAsSolutionRequest. Pentru informații despre clonare și corectare, consultați Creați corectură soluție.

Când efectuați actualizări ale soluției, utilizați StageAndUpgradeRequest și DeleteAndPromoteRequest. Pentru mai multe informații despre procesul de etapizare și upgrade-uri, consultați Upgrade-ul sau actualizarea unei soluții.

Detectarea dependențelor soluțiilor

Acest exemplu arată cum se creează un raport care arată dependențele dintre componentele soluției.

Acest cod va:

  • Recupera toate componentele pentru o soluție.

  • Recupera toate dependențele pentru fiecare componentă.

  • Pentru fiecare dependență găsită se afișează un raport care descrie dependența.

// 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 se află în următorul eșantion de cod.

Raport dependență

Metoda DependencyReport oferă un mesaj mai prietenos bazat pe informațiile găsite în dependență.

Notă

În acest eșantion, metoda este implementată doar parțial. Poate afișa mesaje doar pentru componentele de soluție ale atributului și ale setului de opțiuni.

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

Detectarea măsurii în care componenta soluției poate fi ștearsă

Folosiți mesajul RetrieveDependenciesForDeleteRequest pentru a identifica orice alte componente ale soluției care ar împiedica ștergerea unei componente a soluției date. Următorul eșantion de cod caută orice atribute folosind un set de opțiuni global cunoscut. Orice atribut care utilizează setul global de opțiuni ar împiedica ștergerea setului global de opțiuni.

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