التعامل مع الحلول باستخدام Dataverse SDK

كجزء من دورة حياة التطوير إلى الإنتاج قد ترغب في إنشاء أتمتة مخصصة للتعامل مع مهام معينة. على سبيل المثال، في مسار مشروع DevOps قد ترغب في تنفيذ بعض التعليمات البرمجية المخصصة أو برنامج نصي ينشئ بيئة اختبار معزولة ويستورد حلاً غير مُدار أو يقوم بتصدير حل غير مُدار كحل مُدار، وأخيرًا يخذف البيئة. يمكنك القيام بذلك والمزيد باستخدام واجهات برمجة التطبيقات (API) المتوفرة لك. فيما يلي بعض الأمثلة عما يمكن إنجازه باستخدام Dataverse SDK لـ NET‏‎. والتعليمات البرمجية المخصصة.

‏‫ملاحظة‬

يمكنك أيضًا تنفيذ هذه العمليات نفسها باستخدام واجهة API الويب. الإجراءات المرتبطة هي: ImportSolution وExportSolution وCloneAsPatch وCloneAsSolution.

إنشاء حل غير مُدار أو تصديره أو استيراده

دعنا نتعرف على كيفيه تنفيذ بعض عمليات الحلول الشائعة باستخدام التعليمات البرمجية C#. لعرض العينة الكاملة للتعليمات البرمجية C# الصالحة للعمل التي تبين هذه الأنواع من عمليات الحلول (والمزيد)، راجع العينة: التعامل مع الحلول.

إنشاء ناشر

يحتاج كل حل إلى ناشر، يتمثل بواسطة كيان الناشر. يحتاج الناشر إلى ما يلي:

  • بادئة التخصيص
  • اسم فريد
  • اسم مألوف

‏‫ملاحظة‬

بالنسبة إلى الإدارة السليمة لدورة حياة التطبيقات (ALM)‬، استخدم دائمًا ناشرًا وحلاً مخصصين، وليس الحل والناشر الافتراضيين، لنشر التخصيصات.

تحدد عينة التعليمات البرمجية التالية الناشر أولاً، ثم تتحقق لمعرفة ما إذا كان الناشر موجودًا بالاستناد إلى الاسم الفريد. إذا كان موجودًا، فمن المحتمل أن تكون بادئة التخصيص قد تغيرت، وبالتالي فإن هذه العينة تسعى إلى التقاط بادئة التخصيص الحالية. يتم أيضًا التقاط PublisherId بحيث يمكن حذف سجل الناشر. إذا لم يتم العثور على الناشر، سيتم إنشاء ناشر جديد باستخدام الأسلوب 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;
}

إنشاء حل غير مُدار

بعد أن يتوفر لديك الناشر المخصص، يمكنك بعد ذلك إنشاء حل غير مُدار. يسرد الجدول التالي الحقول التي تحتوي على أوصاف يحتوي عليها الحل.

تسمية الحقل الوصف
الاسم المعروض اسم الحل.
الاسم ينشئ Microsoft Dataverse اسمًا فريدًا يستند إلى الاسم المعروض. يمكنك تحرير الاسم الفريد. يجب أن يحتوي الاسم الفريد على أحرف أبجدية رقمية أو الشرطة السفلية فقط.
الناشر استخدم بحث الناشر‏‎ لربط الحل بناشر.
‏‏الإصدار حدد إصدارًا باستخدام التنسيق التالي: مراجعة.الإصدار.الرئيسي.الثانوي (على سبيل المثال، 1.0.0.0).
صفحة التكوين إذا قمت بتضمين مورد ويب HTML في الحل، فيمكنك استخدام هذا البحث لإضافته كصفحة تكوين حل معينة.
‏‏الوصف استخدم هذا الحقل لتضمين أي تفاصيل ذات صلة بالحل.

فيما يلي عينة تعليمات برمجية لإنشاء حل غير مُدار يستخدم الناشر الذي تم إنشاؤه في القسم السابق.

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

بعد إنشاء حل غير مُدار، يمكنك إضافة أضافه مكونات الحل بإنشائها في سياق هذا الحل أو بإضافة مكونات موجودة من حلول أخرى. مزيد من المعلومات: إضافة مكون حل جديد وإضافة مكون حل موجود

تصدير حل غير مُدار

توضح عينة التعليمات البرمجية هذه كيفية كيفيه تصدير حل غير مُدار أو إنشاء حزمة لحل مُدار. تستخدم التعليمات البرمجية الفئة ExportSolutionRequest لتصدير ملف مضغوط يمثل حلاً غير مُدار. يتم تعيين خيار إنشاء حل مُدار باستخدام الخاصية المُدارة. تقوم هذه العينة بحفظ ملف مسمى samplesolution.zip في مجلد المخرجات.

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

استيراد حل غير مُدار

يتم تنفيذ عملية استيراد (أو ترقية) حل باستخدام التعليمات البرمجية بواسطة ImportSolutionRequest.

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

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

_serviceProxy.Execute(impSolReq);

تعقب نجاح الاستيراد

يمكنك استخدام الكيان ImportJob لالتقاط البيانات المتعلقة بنجاح استيراد الحل. عندما تحدد ImportJobId لـ ImportSolutionRequest، يمكنك استخدام هذه القيمة لاستعلام الكيان ImportJob عن حالة عملية الاستيراد. يمكن أيضًا استخدام ImportJobId لتنزيل ملف سجل استيراد باستخدام الرسالة 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);
   }
}

محتوى الخاصية Data عبارة عن سلسله تمثل ملف XML الخاص بالحل.

إضافة أو إزالة مكونات الحلول

تعرف على كيفية إضافة مكونات الحلول وإزالتها باستخدام تعليمة برمجية.

إضافة مكون حل جديد

يوضح هذا النموذج كيفية إنشاء مكون حل مقترن بحل معين. إذا لم تقم بإقران مكون الحل بحل معين عند إنشائه، لن تتم إضافته إلا إلى الحل الافتراضي وستحتاج لإضافته إلى حل يدويًا أو باستخدام التعليمة البرمجية المضمنة في إضافة مكون حل موجود.

وهذه التعليمة البرمجية تنشئ مجموعة خيارات عمومية جديدة وتضيفها إلى الحل باسم فريد يساوي _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);

إضافة مكون حل موجود

يوضح هذا النموذج كيفية إضافة مكون حل موجود إلى حل.

تستخدم التعليمة البرمجية التالية AddSolutionComponentRequest لإضافة كيان Account كمكون حل إلى الحل غير المدار.

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

إزالة مكون حل

يوضح هذا النموذج كيفية إزالة مكون حل من حل غير مدار. تستخدم التعليمة البرمجية التالية RemoveSolutionComponentRequest لإزالة مكون حل أحد الكيانات من حل غير مدار. يشير solution.UniqueName إلى الحل الذي تم إنشاؤه في إنشاء حل غير مدار.

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

حذف حل

يوضح النموذج التالي كيفية استرداد حل باستخدام uniquename ثم استخراج solutionid من النتائج. ويستخدم النموذج بعد ذلك solutionid مع IOrganizationService. الأسلوب Delete لحذف الحل.

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

النسخ والتصحيح والترقية

يمكنك تنفيذ عمليات حلول إضافية باستخدام واجهات برمجة التطبيقات المتوفرة. بالنسبة للنسخ وتصحيح الحلول، استخدم CloneAsPatchRequest وCloneAsSolutionRequest. لمزيد من المعلومات حول النسخ والتصحيح، راجع إنشاء تصحيحات الحلول.

عند إجراء ترقيات الحلول، استخدم StageAndUpgradeRequest وDeleteAndPromoteRequest. لمزيد من المعلومات حول عملية التجهيز والترقية، راجع ترقية حل أو تحديثه.

الكشف عن تبعيات الحلول

يوضح هذا النموذج كيفية إنشاء تقرير يظهر التبعيات بين مكونات الحلول.

وتتولى هذه التعليمة البرمجية ما يلي:

  • استرداد كافة مكونات الحل.

  • استرداد كافة التبعيات لكل مكون.

  • لكل تبعية تم العثور عليها، تارض تقريرًا يصف التبعية.

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

يوجد الأسلوب DependencyReport في نموذج التعليمة البرمجية التالية.

تقرير التبعية

يوفر الأسلوب DependencyReport رسالة مألوفة للغاية استنادًا إلى المعلومات الموجودة داخل التبعية.

‏‫ملاحظة‬

في هذا النموذج، لا يتم تطبيق الأسلوب إلا بشكل جزئي. ويمكنه عرض رسائل لمكونات حلول مجموعة الخيارات والسمات فقط.

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

الكشف عما إذا كان سيتم حذف مكون الحل

استخدم رسالة RetrieveDependenciesForDeleteRequest لتحديد أي مكونات حلول أخرى قد تمنع حذف مكون حل محدد. يبحث نموذج التعليمات البرمجية التالي عن أي سمات باستخدام مجموعة خيارات عمومية معروفة. ستؤدي أي سمة باستخدام مجموعة الخيارات العمومية إلى منع حذف مجموعة الخيارات العمومية.

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