Làm việc với các giải pháp bằng SDK Dataverse

Là một phần trong vòng đời phát triển đến sản xuất, bạn có thể muốn tạo quy trình tự động hóa tùy chỉnh để xử lý một số tác vụ nhất định. Ví dụ: trong quy trình dự án DevOps, bạn có thể muốn thực thi một số mã hoặc tập lệnh tùy chỉnh để tạo môi trường hộp cát, nhập một giải pháp không được quản lý, xuất giải pháp không được quản lý đó dưới dạng giải pháp được quản lý và cuối cùng là xóa môi trường. Bạn có thể làm điều này và nhiều điều khác bằng cách sử dụng các API có sẵn cho bạn. Dưới đây là một số ví dụ về những điều bạn có thể thực hiện bằng cách sử dụng SDK Dataverse cho .NET và mã tùy chỉnh.

Lưu ý

Bạn cũng có thể thực hiện các thao tác tương tự này bằng API web. Các hành động liên quan là: ImportSolution, ExportSolution, CloneAsPatchCloneAsSolution.

Tạo, xuất hoặc nhập giải pháp không được quản lý

Hãy xem cách thực hiện một số thao tác giải pháp phổ biến bằng cách sử dụng mã C#. Để xem mẫu mã C# hoạt động hoàn chỉnh thể hiện các loại thao tác giải pháp này (và nhiều thao tác khác), hãy xem Mẫu: Làm việc với giải pháp.

Tạo nhà phát hành

Mọi giải pháp đều cần có nhà phát hành, được đại diện bởi thực thể Nhà phát hành. Một nhà phát hành yêu cầu những điều sau:

  • Một tiền tố tùy chỉnh
  • Một tên duy nhất
  • Một tên dễ nhớ

Lưu ý

Để có phương pháp tiếp cận ALM hoạt động tốt, luôn dùng nhà phát hành và giải pháp tùy chỉnh, không dùng giải pháp và nhà phát hành mặc định, để triển khai các tùy chỉnh của bạn.

Mã sau đây xác định nhà phát hành trước, sau đó kiểm tra xem liệu nhà phát hành đã tồn tại hay chưa dựa trên tên duy nhất. Nếu nhà phát hành đã tồn tại, thì tiền tố tùy chỉnh có thể đã bị thay đổi, vì vậy, mẫu này tìm cách ghi lại tiền tố tùy chỉnh hiện tại. PublisherId cũng được ghi lại để có thể xóa bản ghi nhà phát hành. Nếu không tìm thấy nhà phát hành, thì nhà phát hành mới sẽ được tạo bằng cách sử dụng phương pháp 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;
}

Tạo giải pháp không được quản lý

Sau khi có sẵn một nhà phát hành tùy chỉnh, bạn có thể tạo giải pháp không được quản lý. Bảng sau liệt kê các trường với nội dung mô tả có trong giải pháp.

Nhãn của Trường Mô tả
Tên hiển thị Tên của giải pháp.
Tên Microsoft Dataverse tạo một tên duy nhất dựa trên Tên hiển thị. Bạn có thể chỉnh sửa tên duy nhất. Tên duy nhất chỉ được chứa ký tự chữ và số hoặc ký tự dấu gạch dưới.
Nhà phát hành Sử dụng tính năng tra cứu Nhà phát hành để liên kết giải pháp với nhà phát hành.
Phiên bản Chỉ định phiên bằng cách dùng định dạng sau: major.minor.build.revision (ví dụ: 1.0.0.0).
Trang cấu hình Nếu thêm một tài nguyên web HTML vào giải pháp của mình, bạn có thể sử dụng tra cứu này để thêm tài nguyên là trang cấu hình giải pháp được chỉ định.
Mô tả Sử dụng trường này để đưa vào mọi chi tiết có liên quan về giải pháp của bạn.

Dưới đây là mã mẫu để tạo một giải pháp không được quản lý sử dụng nhà phát hành mà chúng tôi đã tạo trong phần trước.

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

Sau khi tạo một giải pháp không được quản lý, bạn có thể thêm các thành phần giải pháp bằng cách tạo chúng trong ngữ cảnh giải pháp này hoặc bằng cách thêm các thành phần hiện có từ giải pháp khác. Thông tin thêm: Thêm thành phần giải pháp mớiThêm thành phần giải pháp hiện có

Xuất giải pháp không được quản lý

Mẫu mã này hướng dẫn cách xuất giải pháp không được quản lý hoặc đóng gói giải pháp được quản lý. Mã này sử dụng lớp ExportSolutionRequest để xuất một tệp nén đại diện cho giải pháp không được quản lý. Tùy chọn tạo giải pháp được quản lý được đặt bằng cách sử dụng thuộc tính Được quản lý. Mẫu này lưu một tệp có tên samplesolution.zip vào thư mục đầu ra.

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

Nhập giải pháp không được quản lý

Nhập (hoặc nâng cấp) giải pháp bằng cách sử dụng mã được thực hiện bằng ImportSolutionRequest.

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

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

_serviceProxy.Execute(impSolReq);

Theo dõi sự thành công của quá trình nhập

Bạn có thể sử dụng thực thể ImportJob để ghi lại dữ liệu về sự thành công của quá trình nhập giải pháp. Khi chỉ định một ImportJobId cho ImportSolutionRequest, bạn có thể sử dụng giá trị đó để truy vấn thực thể ImportJob về trạng thái nhập. ImportJobId cũng có thể dùng để tải xuống tệp nhật ký nhập bằng cách sử dụng thông báo 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);
   }
}

Nội dung của thuộc tính Data là một chuỗi đại diện cho tệp XML giải pháp.

Thêm và loại bỏ thành phần giải pháp

Tìm hiểu cách thêm và loại bỏ thành phần giải pháp bằng mã.

Thêm thành phần giải pháp mới

Mẫu này cho thấy cách tạo thành phần giải pháp được liên kết với một giải pháp cụ thể. Nếu bạn không liên kết thành phần giải pháp với một giải pháp cụ thể khi tạo, thành phần đó sẽ chỉ được thêm vào một giải pháp mặc định. Bạn cần phải thêm thành phần vào giải pháp theo cách thủ công hoặc sử dụng mã có trong Thêm thành phần giải pháp hiện có.

Mã này sẽ tạo ra một bộ tùy chọn chung mới và thêm thành phần vào giải pháp với tên duy nhất bằng _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);

Thêm thành phần giải pháp hiện có

Mẫu này cho thấy cách thêm thành phần giải pháp hiện có vào một giải pháp.

Mã sau đây sử dụng AddSolutionComponentRequest để thêm thực thể Account làm thành phần giải pháp vào một giải pháp không được quản lý.

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

Loại bỏ thành phần giải pháp

Mẫu này cho thấy cách loại bỏ thành phần giải pháp trong một giải pháp không được quản lý. Mã sau đây sử dụng RemoveSolutionComponentRequest để loại bỏ thành phần giải pháp thực thể trong một giải pháp không được quản lý. solution.UniqueName tham chiếu giải pháp được tạo trong Tạo một giải pháp không được quản lý.

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

Xóa một giải pháp

Mẫu sau đây cho thấy cách truy xuất một giải pháp bằng giải pháp uniquename rồi xuất solutionid khỏi kết quả. Sau đó, mẫu này sử dụng solutionid vớiIOrganizationService. Phương pháp Delete để xóa giải pháp.

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

Sao y, vá lỗi và nâng cấp

Bạn có thể thực hiện các hoạt động giải pháp bổ sung bằng cách sử dụng các API có sẵn. Đối với các giải pháp vá lỗi và sao y, hãy sử dụng CloneAsPatchRequestCloneAsSolutionRequest. Để biết thông tin về cách sao y và vá lỗi, hãy xem Tạo bản vá giải pháp.

Khi thực hiện nâng cấp giải pháp, hãy sử dụng StageAndUpgradeRequestDeleteAndPromoteRequest. Để biết thêm thông tin về quá trình tách chuyển và nâng cấp, hãy xem Nâng cấp hoặc cập nhật giải pháp.

Phát hiện quan hệ phụ thuộc giải pháp

Mẫu này cho thấy cách tạo một báo cáo về quan hệ phụ thuộc giữa các thành phần giải pháp.

Mã này sẽ:

  • Truy xuất tất cả thành phần cho một giải pháp.

  • Truy xuất tất cả quan hệ phụ thuộc cho mỗi giải pháp.

  • Với mỗi quan hệ phụ thuộc tìm được, hãy hiển thị một báo cáo mô tả quan hệ phụ thuộc đó.

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

Phương pháp DependencyReport có trong mẫu mã sau đây.

Báo cáo về quan hệ phụ thuộc

Phương pháp DependencyReport cung cấp một thông báo dễ nhớ hơn tùy thuộc vào thông tin tìm được trong quan hệ phụ thuộc.

Lưu ý

Trong mẫu này, phương pháp chỉ được triển khai một phần. Phương pháp này chỉ hiển thị thông báo cho thuộc tính và các thành phần giải pháp của bộ tùy chọn.

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

Phát hiện thành phần giải pháp có thể bị xóa hay không

Sử dụng thông báo RetrieveDependenciesForDeleteRequest để xác định các thành phần giải pháp khác có thể ngăn việc xóa thành phần giải pháp đã đưa ra. Mẫu mã sau đây tìm kiếm bất kỳ thuộc tính nào sử dụng bộ tùy chọn chung đã biết. Bất kỳ thuộc tính nào sử dụng bộ tùy chọn chung đều có thể ngăn việc xóa bộ tùy chọn chung.

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