Chia sẻ qua


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 tính năng 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ể thực hiện những thao tác này và nhiều thao tác khác bằng cách sử dụng các API có sẵn. Sau đây là một số ví dụ về những gì bạn có thể thực hiện bằng cách sử dụng Dataverse SDK 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.

Các mẫu mã trong bài viết này sử dụng các kiểu thực thể ràng buộc sớm được tạo bằng CrmSvcUtil hoặc PAC CLI. Thông tin thêm: Lập trình ràng buộc muộn và ràng buộc sớm sử dụng SDK cho .NET

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

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

Tạo nhà phát hành

Mỗi giải pháp đều yêu cầu một nhà xuất bản, được biểu thị bằng bảng Nhà xuất bản. Nhà xuất bản cần có những đặc tính 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 quản lý vòng đời ứng dụng (ALM) lành mạnh, hãy luôn sử dụng giải pháp và nhà xuất bản tùy chỉnh, không phải giải pháp và nhà xuất bản mặc định, để triển khai các tùy chỉnh của bạn.

Mẫu mã sau đây trước tiên xác định một nhà xuất bản rồi kiểm tra xem nhà xuất bản đó đã tồn tại hay chưa dựa trên tên duy nhất. Nếu đã tồn tại, tiền tố tùy chỉnh có thể đã bị thay đổi. Mẫu này nhằm mục đích nắm bắt 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 đây liệt kê các cột có mô tả về một giải pháp.

Nhãn cột 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ản bằng cách sử dụng định dạng: 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.
Description Sử dụng cột này để đưa vào bất kỳ thông tin chi tiết có liên quan nào về giải pháp của bạn.

Sau đây là mã mẫu để tạo giải pháp không được quản lý sử dụng nhà xuất bản mà chúng ta đã tạo ở 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 bạn tạo giải pháp không được quản lý, hãy thêm các thành phần giải pháp bằng cách tạo chúng trong bối cảnh của giải pháp này hoặc bằng cách thêm các thành phần hiện có từ các 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 bảng ImportJob để thu thập dữ liệu về mức độ thành công của quá trình nhập giải pháp. Khi bạn chỉ định ImportJobId cho ImportSolutionRequest, bạn có thể sử dụng giá trị đó để truy vấn ImportJob bảng về trạng thái của quá trình 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 giải pháp đó được tạo, nó sẽ chỉ được thêm vào giải pháp mặc định và bạn phải thêm nó vào giải pháp theo cách thủ công hoặc bằng cách 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.

Đoạn mã sau sử dụng AddSolutionComponentRequest để thêm Account bảng làm thành phần giải pháp cho 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ý. Đoạn mã sau sử dụng RemoveSolutionComponentRequest để xóa thành phần giải pháp bảng khỏi 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. Delete phương pháp 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 sao chép và vá lỗi, 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ề quy trình dàn dựng và nâng cấp, hãy truy cập 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.

Đoạn mã này thực hiện các hoạt động sau:

  • 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 xem 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 sẽ tìm kiếm bất kỳ thuộc tính nào bằng cách sử dụng cột lựa chọn toàn cục đã biết. Bất kỳ thuộc tính nào sử dụng lựa chọn toàn cục sẽ ngăn không cho lựa chọn toàn cục đó bị xóa.

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