介绍并配置Dynamics 365中的虚拟实体Virtual Entity

风流不在谈锋胜,袖手无言味最长。这篇文章主要讲述介绍并配置Dynamics 365中的虚拟实体Virtual Entity相关的知识,希望能为你提供帮助。
我是微软Dynamics 365 & Power Platform方面的工程师/顾问罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面的微软最有价值专家(Microsoft MVP),欢迎关注我的微信公众号 MSFTDynamics365erLuoYong ,回复429或者20201219可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!
虚拟实体是Dynamics 365 CE V9版本新增的功能,官方文档请参考  ??Create and edit virtual entities that contain data from an external data source?? 系列文章,还有一个旧版的文档  ??Interact with data from external systems using the new virtual entities?? 。
我这里做个简单的实验,就是我的Tenant有两个Dynamics 365 CE实例,分别为  https://luoyong.crm5.dynamics.com/ 和  https://logicalinventorycenter.crm5.dynamics.com/ ,我想在前面实例中展示后面实例的数据,因为Dynamics 365 CE是支持OData V4的,所以应该配置下就可以。关于要求等可以参考官方文档 ? ?OData v4 Data Provider configuration, requirements, and best practices?? 。
  我这里主要参考  ??Virtual entity walkthrough using the OData v4 Data Provider?? 来配置。
登录Dynamics 365 CE后导航到  Settings  >   Administration  >   Virtual Entity Data Sources ,点击NEW按钮,在弹出框中选择 OData V4 Data Provider,然后点击 OK 按钮。

   
我这里录入的信息如下,保存。

 
然后新建一个虚拟实体,如下:与非虚拟实体不同的是,Virtual Entity这个选项一定要选中,要选择Data Source,我这里就选择前面建立的,然后Extenal Name 和 Extenal Collection Name一定要赋值。这个值从哪儿来呢?从数据提供方的元数据来,比如我这里就来自  https://logicalinventorycenter.crm5.dynamics.com/api/data/v9.1/$metadata .

   
Extenal Name就来自 如下图所示的位置:

 
  保存后会自动产生两个字段,分别是主属性和主键,注意要设置下这两个字段的 External Name并保存。比如我这里为主属性ly_name设置的Extenal Name的值如下:

 
 
主键字段注意也要设置下Extenal Name字段的并保存,这个字段要映射到数据提供方的一个数据类型为  Edm.Guid 类型的字段。

  当然还可以增加其他字段,例如如下字段。对于Optionset字段稍微麻烦点,Lookup字段似乎不好映射。

 
If the field type you create is OptionSet, the following additional fields are available.

  • External Type Name. This property maps to the external name of the set of values in the external service for the option set. Typically, this can be an enum or name of a string value class. The External Type Name can be used when a fully qualified name is required. For example, as the  Type Name  with OData where parameters in a query need the fully qualified name, such as [Type Name].[Value].
  • External Value. This property maps to the corresponding value in the external data source for the option set item. This value entered is used to determine which option set item to display in the app.
然后修改下实体的视图和窗体发布下,去看效果,发现我的报错。
【介绍并配置Dynamics 365中的虚拟实体Virtual Entity】
   
界面上显示的错误代码是  0x80050263 ,然后我去后台搜了下错误信息(虽然我开启了所有的插件日志,但是插件日志中并无错误消息,应该是没有触发),应该是
ErrorCode: 0x80040216
Message: System.Net.WebException: The remote server returned an error: (401) Unauthorized.
然后如何传递认证信息呢?我们知道Dynamics 365支持很多种认证,但是在配置Data Source的时候不是很好配置,比如说配置OAuth 2认证。
那我就自己获取一个token,具体参考  ??Dynamics 365 Online通过OAuth 2 Client Credential授权(Server-to-Server Authentication)后调用Web API?? ,我想配置进去,如下:

 
  但是发现一个问题,token比较长,复制粘贴到Value的时候粘贴不完,这个字段的长度是1024,不够长。那可以改吗?
我开始用Web API来改,代码如下,更改字段长度的时候总是报错,An error occurred while validating input parameters: Microsoft.OData.ODataException: Does not support untyped value in non-open type.
var entiyLogicalName = "msdyn_odatav4ds";
var fieldLogicalName = "msdyn_parameter1value";
var fieledMetadataId = "";
var entityMetadataId = "";
var responseJSON = {};
var attributesToChange = [];
var clientURL = Xrm.Utility.getGlobalContext().getClientUrl();
var req = new XMLHttpRequest();
req.open("GET", encodeURI(clientURL + "/api/data/v9.1/EntityDefinitions?$select=MetadataId& $filter=LogicalName eq " + entiyLogicalName + ""), false);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.onreadystatechange = function () {
if (this.readyState == 4) {
req.onreadystatechange = null;
if (this.status == 200) {
responseJSON = JSON.parse(this.responseText);
entityMetadataId = responseJSON.value[0].MetadataId;
}
}
}
req.send();
console.log(entiyLogicalName + ".MetadataId=" + entityMetadataId);
req = new XMLHttpRequest();
req.open("GET", encodeURI(clientURL + "/api/data/v9.1/EntityDefinitions(LogicalName=" + entiyLogicalName + ")/Attributes/?$filter=LogicalName eq " + fieldLogicalName + "& $select=MetadataId,LogicalName"), false);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.onreadystatechange = function () {
if (this.readyState == 4) {
req.onreadystatechange = null;
if (this.status == 200) {
responseJSON = JSON.parse(this.responseText);
fieledMetadataId = responseJSON.value[0].MetadataId;
}
}
}
req.send();

req = new XMLHttpRequest();
req.open("PUT", encodeURI(clientURL + "/api/data/v9.1/EntityDefinitions(" + entityMetadataId + ")/Attributes(" + fieledMetadataId + ")"), false);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.onreadystatechange = function () {
if (this.readyState == 4 /* complete */) {
req.onreadystatechange = null;
if (this.status == 204) {
console.log("修改成功!");
}
else {
var error = JSON.parse(this.response).error;
Xrm.Utility.alertDialog("修改出错." + error.message);
}
}
};
var sendData = https://www.songbingjia.com/android/{
"MaxLength": 100
};
req.send(JSON.stringify(sendData));
console.log("更新字段最大长度成功,下一步发布本实体!");

var PublishXmlRequest = function (parameterXml) {
this.ParameterXml = parameterXml;
};
PublishXmlRequest.prototype.getMetadata = https://www.songbingjia.com/android/function () {
return {
boundParameter: null,
parameterTypes: {
"ParameterXml": {
typeName: "Edm.String",
structuralProperty: 1
}
},
operationType: 0,
operationName: "PublishXml",
};
};

var parameterXml = "< publish> < entities> < entity> " + entiyLogicalName + "< /entity> < /entities> < /publish> ";
var publishXmlRequest = new PublishXmlRequest(parameterXml);
Xrm.WebApi.online.execute(publishXmlRequest).then(
function (result) {
if (result.ok) {
console.log("发布实体操作完成! Status: %s %s", result.status, result.statusText);
}
},
function (error) {
console.log(error.message);
}
);

然后我用C#代码通过组织服务来改,虽然不报错,但是没有效果,后来才发现是因为这个实体是虚拟实体,是不可定制的实体,所以没有办法改。这种C#+组织服务的方法是可以改字段长度的(我现在也没有搞懂为啥Web API一改就报错),只是这里不可定制导致改了没有效果而已。相关官方文档请参考  ? ?RetrieveAttributeRequest Class??  ,??UpdateAttributeRequest Class?? 和  ??PublishXmlRequest Class?? 。
var entiyLogicalName = "msdyn_odatav4ds";
var fieldLogicalName = "msdyn_parameter1value";
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
Console.WriteLine($"罗勇的测试程序...请输入Y继续!");
var input = Console.ReadLine().ToString().ToUpper();
if (input == "Y")
{
CrmServiceClient crmSourceSvc = new CrmServiceClient(ConfigurationManager.AppSettings["ConnStr"]);
if (!crmSourceSvc.IsReady)
{
throw new Exception("连接ConnStr失败!" + crmSourceSvc.LastCrmError);
}
else
{
Console.WriteLine($"程序连接到Dynamics 365/Power Apps环境 ConnStr 成功!");
}
RetrieveAttributeRequest attributeRequest = new RetrieveAttributeRequest
{
EntityLogicalName = entiyLogicalName,
LogicalName = fieldLogicalName,
RetrieveAsIfPublished = true
};
RetrieveAttributeResponse attributeResponse =
(RetrieveAttributeResponse)crmSourceSvc.Execute(attributeRequest);
Console.WriteLine("获取属性成功!");
AttributeMetadata retrievedAttributeMetadata = https://www.songbingjia.com/android/attributeResponse.AttributeMetadata;
StringAttributeMetadata updateAttributeMetadata = https://www.songbingjia.com/android/retrievedAttributeMetadata as StringAttributeMetadata;
updateAttributeMetadata.MaxLength = 2048;
UpdateAttributeRequest updateRequest = new UpdateAttributeRequest
{
Attribute = updateAttributeMetadata,
EntityName = entiyLogicalName,
MergeLabels = false
};
crmSourceSvc.Execute(updateRequest);
Console.WriteLine("更改属性成功!");
var pxReq = new PublishXmlRequest { ParameterXml = $"< publish> < entities> < entity> {entiyLogicalName}< /entity> < /entities> < /publish> " };
crmSourceSvc.Execute(pxReq);
Console.WriteLine("发布实体成功!");

 
至于问题怎么解决,请关注后续更新。



    推荐阅读