Bagikan melalui


Bekerja dengan solusi menggunakan SDK Dataverse

Sebagai bagian dari pengembangan siklus hidup produksi, Anda mungkin ingin membuat otomatisasi kustom untuk menangani tugas tertentu. Misalnya, di alur proyek devops, anda mungkin ingin mengeksekusi beberapa kode kustom atau skrip yang membuat lingkungan sandbox, mengimpor solusi yang tidak terkelola, mengekspor solusi yang tidak terkelola sebagai solusi terkelola, dan, akhirnya, menghapus lingkungan. Anda dapat melakukannya dan lainnya dengan menggunakan api yang tersedia untuk Anda. Di bawah ini beberapa contoh hal yang dapat Anda capai menggunakan Dataverse SDK for .NET dan kode kustom.

Catatan

Anda juga dapat melakukan operasi yang sama ini menggunakan web API. Tindakan yang terkait adalah: ImportSolution, ExportSolution, CloneAsPatch, and CloneAsSolution.

Buat, ekspor, atau impor solusi tidak terkelola

Mari Lihat cara melakukan beberapa operasi solusi umum menggunakan kode C#. Untuk melihat sampel kode bekerja C# lengkap yang menunjukkan jenis operasi solusi (dan lainnya), lihat sampel: bekerja dengan solusi.

Membuat Penerbit

Setiap solusi memerlukan penerbit yang ditunjukkan oleh Entitas penerbit. Penerbit memerlukan yang berikut:

  • Prefiks Penyesuaian
  • Nama unik
  • Nama akrab

Catatan

Untuk pendekatan ALM yang sehat, selalu gunakan penerbit dan solusi kustom, bukan solusi default dan penerbit, untuk menyebarkan penyesuaian.

Sampel kode berikut pertama mendefinisikan penerbit dan kemudian memeriksa untuk melihat apakah penerbit sudah ada berdasarkan nama unik. Jika sudah ada, awalan penyesuaian mungkin telah diubah, sehingga sampel ini berupaya menangkap awalan penyesuaian saat ini. PublisherId juga akan dicatat sehingga data penerbit dapat dihapus. Jika penerbit tidak ditemukan, penerbit baru akan dibuat menggunakan 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;
}

Membuat solusi tidak terkelola

Setelah memiliki penerbit kustom yang tersedia, Anda kemudian dapat membuat solusi yang tidak terkelola. Tabel berikut mencantumkan bidang dengan deskripsi yang termuat dalam solusi.

Label Bidang Keterangan
Nama Tampilan Nama untuk solusi.
Nama Microsoft Dataverse menghasilkan nama unik berdasarkan Nama Tampilan. Anda dapat mengedit nama unik. Nama unik hanya boleh berisi karakter alfanumerik dan karakter garis bawah.
Penerbit Gunakan pencarian Penerbit untuk mengaitkan solusi dengan penerbit.
Versi Tentukan versi dengan menggunakan format berikut: besar.kecil.build.revisi, (misalnya, 1.0.0.0.
Halaman Konfigurasi Jika Anda menyertakan sumber daya web HTML di solusi, Anda dapat menggunakan pencarian ini untuk menambahkannya sebagai halaman konfigurasi solusi yang ditentukan.
Deskripsi Gunakan bidang ini untuk menyertakan rincian yang relevan tentang solusi Anda.

Di bawah ini adalah kode contoh untuk membuat solusi tidak terkelola yang menggunakan penerbit yang kami buat di bagian sebelumnya.

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

Setelah membuat solusi yang tidak terkelola, Anda dapat menambahkan komponen solusi dengan membuatnya dalam konteks solusi ini atau dengan menambahkan komponen yang ada dari solusi lain. Informasi selengkapnya: menambahkan komponen solusi baru dan menambahkan komponen solusi yang ada

Ekspor solusi tak terkelola

Sampel kode ini menunjukkan cara mengekspor solusi tidak terkelola atau mengemas solusi terkelola. Kode tersebut menggunakan kelas ExportSolutionRequest untuk mengekspor file terkompresi yang mewakili solusi tidak terkelola. Opsi untuk membuat solusi terkelola diatur menggunakan properti Terkelola. Contoh ini menyimpan file bernama samplesolution.zip ke folder output.

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

Impor solusi tak terkelola

Mengimpor (atau mengupgrade) solusi dengan menggunakan kode dicapai dengan importsolutionrequest.

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

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

_serviceProxy.Execute(impSolReq);

Melacak keberhasilan impor

Anda dapat menggunakan entitas ImportJob untuk mengambil data tentang keberhasilan impor solusi. Bila Anda menentukan ImportJobId untuk importsolutionrequest, Anda dapat menggunakan nilai tersebut untuk kueri entitas importjob tentang status impor. ImportJobId juga dapat digunakan untuk mengunduh file log impor menggunakan pesan 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);
   }
}

Konten properti Data adalah string yang mewakili file XML solusi.

Tambahkan dan hapus komponen solusi

Pelajari cara menambahkan dan menghapus komponen solusi menggunakan kode.

Menambahkan komponen solusi baru

Sampel ini menunjukkan cara membuat komponen solusi yang terkait dengan solusi tertentu. Jika Anda tidak mengaitkan komponen solusi ke solusi tertentu saat dibuat, maka hanya akan ditambahkan ke solusi default dan Anda perlu menambahkannya ke solusi secara manual atau menggunakan kode yang disertakan dalam tambah komponen solusi yang ada.

Kode ini membuat rangkaian pilihan global baru dan menambahkannya ke solusi dengan nama unik yang sama dengan _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);

Tambah komponen yang ada ke komponen solusi

Sampel ini menunjukkan cara menambahkan komponen solusi yang ada ke solusi.

Kode berikut menggunakan AddSolutionComponentRequest untuk menambahkan entitas Account sebagai komponen solusi ke solusi yang tidak terkelola.

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

Hapus komponen solusi

Sampel ini menunjukkan cara menghapus komponen solusi dari solusi tidak terkelola. Kode berikut menggunakan RemoveSolutionComponentRequest untuk menghapus komponen solusi entitas dari solusi yang tidak terkelola. Solusi solution.UniqueName merujuk pada solusi yang dibuat dalam membuat solusi tidak terkelola.

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

Menghapus solusi

Contoh berikut menunjukkan cara mengambil solusi menggunakan solusi uniquename dan kemudian mengekstrak solutionid dari hasil. Sampel kemudian menggunakan solutionid denganIOrganizationService. Delete metode untuk menghapus solusi.

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

Kloning, patching, dan peningkatan

Anda dapat melakukan operasi solusi tambahan menggunakan api yang tersedia. Untuk solusi kloning dan patching menggunakan cloneaspatchrequest dan cloneassolutionrequest. Untuk informasi tentang kloning dan patching, lihat membuat patch solusi.

Ketika melakukan upgrade solusi gunakan stageandupgraderequest dan deleteandpromoterequest. Untuk informasi lebih lanjut tentang proses staging dan peningkatan, lihat meningkatkan atau memperbarui solusi.

Mendeteksi dependensi Solusi

Sampel ini menunjukkan cara membuat laporan yang menunjukkan dependensi antara komponen solusi.

Kode ini akan:

  • Mengambil semua komponen untuk solusi.

  • Mengambil semua dependensi untuk setiap komponen.

  • Untuk setiap dependensi yang ditemukan tampilkan laporan yang menjelaskan dependensi.

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

Metode DependencyReport berada di sampel kode berikut.

Laporan dependensi

Metode DependencyReport menyediakan pesan lebih ramah berdasarkan informasi yang ditemukan dalam dependensi.

Catatan

Dalam contoh ini metode ini hanya diimplementasikan sebagian. Ia hanya dapat menampilkan pesan untuk komponen atribut dan solusi rangkaian pilihan.

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

Deteksi apakah komponen solusi dapat dihapus

Gunakan pesan RetrieveDependenciesForDeleteRequest untuk mengidentifikasi komponen solusi lainnya yang akan mencegah komponen solusi tertentu dihapus. Sampel kode berikut mencari atribut yang menggunakan rangkaian pilihan global yang diketahui. Atribut apa pun yang menggunakan rangkaian pilihan global akan mencegah rangkaian pilihan global dihapus.

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