Delo z rešitvami ob uporabi razvojnega kompleta SDK okolja Dataverse

V okviru življenjskega cikla od razvoja do proizvodnje lahko ustvarite avtomatizacijo po meri za obravnavo nekaterih opravil. Tako lahko na primer pri razvoju projekta DevOps izvedete kodo ali skript po meri, ki ustvari preizkusno okolje, uvozi neupravljano rešitev, izvozi to neupravljano rešitev kot upravljano rešitev in na koncu izbriše okolje. To in še več lahko naredite prek API-jev, ki so vam na voljo. Spodaj je nekaj primerov tega, kar lahko dosežete z ogrodjem SDK okolja Dataverse za .NET in kodo po meri.

opomba,

Te postopke lahko izvedete tudi prek spletnega API-ja. Povezana dejanja so: ImportSolution, ExportSolution, CloneAsPatch in CloneAsSolution.

Ustvarjanje, izvoz ali uvoz neupravljane rešitve

Oglejmo si, kako izvesti nekaj pogostih postopkov rešitev z uporabo kode C#. Za ogled celotnega delujočega vzorca kode C#, ki ponazarja te vrste postopkov rešitev (in drugo), glejte Vzorec: delo z rešitvami.

Ustvarjanje izdajatelja

Vsaka rešitev zahteva izdajatelja, ki ga predstavlja entiteta izdajatelja. Za izdajatelja je treba zagotoviti:

  • Predpono prilagoditve
  • Enolično ime
  • Prijazno ime

opomba,

Za zagotavljanje ustreznega pristopa k upravljanju življenjskega cikla aplikacije pri uvajanju prilagoditev vedno uporabite izdajatelja in rešitev po meri namesto privzete rešitve in izdajatelja.

Naslednji vzorec kode najprej določi izdajatelja in nato na podlagi enoličnega imena preveri, ali izdajatelj že obstaja. Če že obstaja, se je predpona prilagoditve morda spremenila, zato vzorec poskusi zajeti trenutno predpono prilagoditve. Zajame se tudi PublisherId, tako da je zapis izdajatelja mogoče izbrisati. Če izdajatelja ni mogoče najti, se na podlagi metode IOrganizationService.Create.Create ustvari nov izdajatelj.

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

Ustvarjanje neupravljane rešitve

Ko imate na voljo izdajatelja po meri, lahko ustvarite neupravljano rešitev. V naslednji tabeli so navedena polja z opisi, ki jih vsebuje rešitev.

Oznaka polja Opis
Prikazano ime Ime rešitve.
Imenu Microsoft Dataverse ustvari enolično ime na podlagi prikaznega imena. Enolično ime lahko uredite. Enolično ime je lahko sestavljeno le iz alfanumeričnih znakov ali podčrtaja.
Izdajatelj Uporabite iskanje Izdajatelj, da povežete rešitev z izdajateljem.
Različica Določite različico v naslednji obliki zapisa: glavna_različica.podrazličica.delovna_različica.revizija. (na primer 1.0.0.0.).
Stran s konfiguracijo Če v svojo rešitev vključite spletni vir HTML, ga lahko prek tega iskanja dodate kot namensko stran s konfiguracijo rešitve.
Opis V to polje vključite ustrezne podatke o rešitvi.

Spodaj je vzorčna koda za ustvarjanje neupravljane rešitve, ki uporablja izdajatelja, ustvarjenega v prejšnjem razdelku.

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

Ko ustvarite neupravljano rešitev, lahko dodate komponente rešitve tako, da jih ustvarite v okviru te rešitve ali pa dodate obstoječe komponente iz drugih rešitev. Več informacij: Dodajanje nove komponente rešitve in Dodajanje obstoječe komponente rešitve

Izvoz neupravljane rešitve

Ta vzorec kode ponazarja, kako izvoziti neupravljano rešitev ali zapakirati upravljano rešitev. Koda uporablja razred ExportSolutionRequest za izvoz stisnjene datoteke, ki predstavlja neupravljano rešitev. Možnost za ustvarjanje upravljane rešitve se nastavi z uporabo lastnosti Managed. Ta vzorec shrani datoteko samplesolution.zip v izhodno mapo.

// 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 neupravljane rešitve

Uvoz (ali nadgradnja) rešitve prek kode se izvede na podlagi ImportSolutionRequest.

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

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

_serviceProxy.Execute(impSolReq);

Spremljanje uspešnosti uvoza

Entiteto ImportJob lahko uporabite za zajem podatkov o uspešnosti uvoza rešitve. Ko določite ImportJobId za ImportSolutionRequest, lahko uporabite to vrednost za poizvedbo entitete ImportJob glede stanja uvoza. ImportJobId lahko uporabite tudi za prenos datoteke z dnevnikom uvoza prek sporočila 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);
   }
}

Vsebina lastnosti Data je niz, ki predstavlja datoteko XML rešitve.

Dodajanje in odstranjevanje komponent rešitve

Preberite, kako lahko s kodo dodate in odstranite komponente rešitve.

Dodajanje nove komponente rešitve

Ta primer prikazuje, kako ustvarite komponento rešitve, ki je povezana z določeno rešitvijo. Če komponente rešitve ne povežete z določeno rešitvijo, ko je ustvarjena, bo dodana samo v privzeto rešitev in jo boste morali v rešitev dodati ročno ali z uporabo kode, ki je vključena v koraku Dodajanje obstoječe komponente rešitve.

Ta koda ustvari nov globalni nabor možnosti in ga doda v rešitev z enoličnim imenom, ki je enako _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);

Dodajanje obstoječe komponente rešitve

Ta primer prikazuje, kako dodate obstoječo komponento rešitve v rešitev.

Spodnja koda uporablja AddSolutionComponentRequest za dodajanje entitete Account kot komponente rešitve za neupravljano rešitev.

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

Odstranjevanje komponente rešitve

Ta primer prikazuje, kako odstranite komponento rešitve iz neupravljane rešitve. Spodnja koda uporablja RemoveSolutionComponentRequest za odstranjevanje komponente rešitve entitete iz neupravljane rešitve. solution.UniqueName se sklicuje na rešitev, ustvarjeno v koraku Ustvarjanje neupravljane rešitve.

// 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 rešitve

Spodnji primer prikazuje, kako pridobite rešitev z uporabo uniquename rešitve in nato izvlečete solutionid iz rezultatov. Primer nato uporabi solutionid s IOrganizationService. Metoda Delete za brisanje rešitve.

// 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, popravki in nadgradnja

Dodatne postopke rešitve lahko izvedete z API-ji, ki so na voljo. Za kloniranje in popravljanje rešitev uporabite CloneAsPatchRequest in CloneAsSolutionRequest. Za informacije o kloniranju in popravkih glejte Ustvarjanje popravkov rešitev.

Pri nadgradnjah rešitve uporabite StageAndUpgradeRequest in DeleteAndPromoteRequest. Za več informacij o postopku priprave in nadgradenj glejte Nadgradnja ali posodobite rešitve.

Iskanje odvisnosti rešitve

Ta primer prikazuje, kako ustvarite poročilo, ki prikazuje odvisnosti med komponentami rešitve.

Ta koda:

  • pridobi vse komponente za rešitev.

  • pridobi vse odvisnosti za vsako komponento.

  • prikaže poročilo, ki opisuje odvisnost, za vsako najdeno odvisnost.

// 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 spodnjem primeru kode.

Poročilo o odvisnosti

Metoda DependencyReport ponuja prijaznejše sporočilo na podlagi informacij, najdenih v odvisnosti.

opomba,

V tem primeru je metoda uporabljena le deloma. Sporočila lahko prikaže samo za komponente rešitve atributa in nabora mož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);
}

Zaznavanje, ali je mogoče izbrisati komponento rešitve

Uporabite sporočilo RetrieveDependenciesForDeleteRequest, da poiščete druge komponente rešitve, ki bi preprečile brisanje določene komponente rešitve. Spodnji primer kode poišče vse atribute z uporabo znanega globalnega nabora možnosti. Vsak atribut, ki uporablja globalni nabor možnosti, preprečuje brisanje globalnega nabora mož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);
  }
 }
}