עבוד עם פתרונות באמצעות Dataverse SDK
כחלק ממחזור החיים של פיתוח לייצור שלך, ייתכן שתרצה ליצור אוטומציה מותאמת אישית לטיפול במשימות מסוימות. לדוגמה, בקו הצינור של פרוייקט DevOps שלך, ייתכן שתרצה לבצע קוד או קובץ Script מותאמים אישית שיוצרים סביבת ארגז חול (sandbox), מייבאים פתרון לא מנוהל, מייצאים פתרון לא מנוהל זה כפתרון מנוהל ולבסוף, מוחקים את הסביבה. באפשרותך לעשות זאת ועוד באמצעות ממשקי ה- 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 יוצר שם ייחודי בהתבסס על שם התצוגה. באפשרותך לערוך את השם הייחודי. השם הייחודי מוכרח להכיל רק תווים אלפאנומריים או תו מקף תחתון. |
מפרסם | השתמש בבדיקת המידע של מפרסם כדי לשייך את הפתרון למפרסם. |
גירסה | ציין גירסה באמצעות התבנית הבאה: major.minor.build.revision (לדוגמה, 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);
באפשרותך לבצע פעולות פתרון נוספות באמצעות ממשקי ה- API הזמינים. עבור פתרונות שכפול ותיקון, השתמש ב- 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);
}
}
}