Share via


Rad s rješenjima upotrebom SDK Dataverse

U sklopu razvoja životnog ciklusa proizvodnje možda ćete htjeti stvoriti prilagođenu automatizaciju za upravljanje određenim zadacima. Na primjer, na svom kanalu za DevOps projekt možda želite izvršiti prilagođeni kôd ili skriptu koja stvara testno okruženje, uvozi neupravljano rješenje, izvozi to neupravljano rješenje kao upravljano rješenje i, na kraju, briše okruženje. To i druge radnje možete izvršiti upotrebom API-ja koji su vam dostupni. U nastavku slijedi nekoliko primjera onoga što možete postići pomoću Dataverse SDK za .NET i prilagođenog koda.

Napomena

Te operacije možete izvršiti i upotrebom Web API-ja. Povezane radnje: Uvoz rješenja, Izvoz rješenja, Kloniranje kao zakrpa i Kloniranje kao rješenje.

Stvaranje, izvoz ili uvoz neupravljanog rješenja

Pogledajmo kako izvesti neke uobičajene operacije rješenja pomoću koda C#. Da biste pregledali cjeloviti uzorak funkcionalnog koda C# koji demonstrira te vrste operacija rješenja (itd.), pogledajte Uzorak: rad s rješenjima.

Stvaranje izdavača

Svako rješenje zahtijeva izdavača kojeg predstavlja Entitet izdavača, Izdavač zahtijeva sljedeće:

  • Prefiks prilagođavanja
  • Jedinstveni naziv
  • Neslužbeni naziv

Napomena

Za ispravan ALM pristup uvijek koristite prilagođenog izdavača i rješenje, a ne zadano rješenje i izdavača za implementaciju prilagođavanja.

Sljedeći uzorak koda najprije definira izdavača, a zatim provjerava postoji li već izdavač na temelju jedinstvenog naziva. Ako već postoji, prefiks prilagođavanja možda je izmijenjen, pa ovaj uzorak nastoji zabilježiti trenutačni prefiks prilagođavanja. Bilježi se i PublisherId pa se zapis izdavača može izbrisati. Ako izdavač nije pronađen, novi izdavač stvara se pomoću metode 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;
}

Stvaranje neupravljanog rješenja

Kada vam je dostupan prilagođeni izdavač, možete stvoriti neupravljano rješenje. Sljedeća tablica sadrži polja s opisima koje rješenje sadrži.

Oznaka polja Opis
Prikazni naziv Naziv rješenja.
Naziv Microsoft Dataverse generira jedinstveni naziv na temelju zaslonskog naziva. Jedinstveni naziv možete urediti. Jedinstveni naziv smije sadržavati samo alfanumeričke znakove ili podvlaku.
Izdavač Koristite referentno polje Izdavač da biste rješenje povezali s izdavačem.
Verzija Navedite verziju u sljedećem formatu: glavna.sporedna.ugrađena.izmjena (na primjer, 1.0.0.0.
Konfiguracijska stranica Ako u svoje rješenje uključite HTML web-resurs, možete upotrijebiti referentno polje kako biste ga dodali kao svoju konfiguracijsku stranicu rješenja.
Opis Upotrijebite ovo polje da biste uključili sve relevantne pojedinosti o svom rješenju.

U nastavku je naveden uzorak koda za stvaranje neupravljanog rješenja koje koristi izdavač stvoren u prethodnom odjeljku.

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

Nakon što stvorite neupravljano rješenje, možete dodati komponente rješenja stvarajući ih u kontekstu ovog rješenja ili dodavanjem postojećih komponenata iz drugih rješenja. Dodatne informacije: Dodavanje nove komponente rješenja i Dodavanje postojeće komponente rješenja

Uvoz neupravljanog rješenja

Ovaj uzorak koda prikazuje kako izvesti neupravljano rješenje ili pakirati upravljano rješenje. Kôd koristi klasu ExportSolutionRequest za izvoz sažete datoteke koja predstavlja neupravljano rješenje. Mogućnost stvaranja upravljanog rješenja postavlja se pomoću svojstva Upravljano. Ovaj uzorak sprema datoteku naziva samplesolution.zip u izlaznu mapu.

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

Uvoz neupravljanog rješenja

Uvoz (ili nadogradnja) rješenja pomoću koda vrši se pomoću zahtjeva za uvoz rješenja.

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

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

_serviceProxy.Execute(impSolReq);

Praćenje uspjeha uvoza

ImportJob entitet možete koristiti za bilježenje podataka o uspjehu uvoza rješenja. Kad navedete ImportJobId za zahtjev za uvoz rješenja, možete koristiti tu vrijednost za izvršavanje upita za entitet posla uvoza o statusu uvoza. ImportJobId također se može koristiti za preuzimanje datoteke zapisnika uvoza pomoću poruke Zahtjev za dohvaćanje formatiranih poslova uvoza.

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

Sadržaj entiteta Data jest niz koji predstavlja rješenje XML datoteke.

Dodavanje i uklanjanje komponenti rješenja

Saznajte kako dodati i ukloniti komponente rješenja pomoću koda.

Dodavanje nove komponente rješenja

Ovaj uzorak pokazuje kako stvoriti komponentu rješenja koja je povezana s određenim rješenjem. Ako komponentu rješenja ne pridružite određenom rješenju kad se stvori, dodat će se samo u zadano rješenje i morat ćete je dodati rješenju ručno ili pomoću koda koji je uključen u Dodavanje postojeće komponentu rješenja.

Ovaj kôd stvara novi globalni skup mogućnosti i dodaje ga rješenju s jedinstvenim nazivom jednakim _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);

Dodavanje postojeće komponente rješenja

Ovaj uzorak pokazuje kako dodati postojeću komponentu rješenja u rješenje.

Sljedeći kôd koristi AddSolutionComponentRequest za dodavanje entiteta Account kao komponente rješenja neupravljanom rješenju.

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

Uklanjanje komponente rješenja

Ovaj uzorak pokazuje kako ukloniti postojeću komponentu rješenja iz neupravljanog rješenja. Sljedeći kôd koristi RemoveSolutionComponentRequest za uklanjanje komponente rješenja entiteta iz neupravljanog rješenja. solution.UniqueName upućuje na rješenje stvoreno u Stvaranje rješenja bez upravljanja.

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

Brisanje rješenja

Sljedeći uzorak pokazuje kako dohvatiti rješenje korištenjem rješenja uniquename, a zatim izvući solutionid iz rezultata. Uzorak tada koristi solutionid s IOrganizationService. Metoda Delete za brisanje rješenja.

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

Kloniranje, stvaranje zakrpa i nadogradnja

Dodatne operacije rješenja možete izvesti pomoću dostupnih API-ja. Za kloniranje i zakrpavanje rješenja koristite CloneAsPatchRequest i CloneAsSolutionRequest. Za informacije o kloniranju i primjeni zakrpa pogledajte Stvaranje zakrpa rješenja,

Prilikom nadogradnje rješenja koristite StageAndUpgradeRequest i DeleteAndPromoteRequest. Za više informacija o postupku ukidanja i nadogradnje pogledajte Nadogradite ili ažurirajte rješenje.

Otkrivanje zavisnosti rješenja

Ovaj uzorak pokazuje kako stvoriti izvješće koje pokazuje ovisnosti između komponenata rješenja.

Ovaj kod će:

  • Dohvatiti sve komponente za rješenje.

  • Dohvatiti sve ovisnosti za svaku komponentu.

  • Za svaku pronađenu ovisnost prikazati izvješće koje opisuje ovisnost.

// 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 u sljedećem uzorku koda.

Izvješće o ovisnosti

Metoda DependencyReport pruža prijateljsku poruku koja se temelji na podacima pronađenima u ovisnosti.

Napomena

U ovom se primjeru metoda samo djelomično provodi. Može prikazivati poruke samo za komponente rješenja atributa i skupa mogućnosti.

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

Otkrivanje može li se komponenta rješenja izbrisati

Koristite poruku RetrieveDependenciesForDeleteRequest za prepoznavanje svih ostalih komponenti rješenja koje bi spriječile brisanje dane komponente rješenja. Sljedeći uzorak koda traži sve atribute koristeći poznati globalni skup mogućnosti. Svaki atribut koji koristi globalni skup mogućnosti spriječio bi brisanje globalnog skupa mogućnosti.

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