采得百花成蜜后,为谁辛苦为谁甜。这篇文章主要讲述使用自定义数据源配置虚拟实体相关的知识,希望能为你提供帮助。
我是微软Dynamics 365 &
Power Platform方面的工程师/顾问罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面的微软最有价值专家(Microsoft MVP),欢迎关注我的微信公众号 MSFTDynamics365erLuoYong ,回复431或者20201221可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!
前面的博文 介绍并配置Dynamics 365中的虚拟实体Virtual Entity 讲了通过配置的方法来使用虚拟实体,今天我们来讲需要编码才能使用的虚拟实体。主要参考的官方文档包括 Custom virtual entity data providers 和 Sample: Generic virtual entity data provider plug-in 。
这首先需要创建自定义数据提供方(custom data provider),分为两种自定义数据提供方,分别是通用的自定义数据提供方(Generic)和特定的自定义数据提供方(Targeted),我们今天演示下通用的自定义数据提供方来为虚拟实体提供数据。
和创建插件程序集差不多,需要创建一个框架为.NET Framework 4.6.2 的 Class Library (.NET Framework) 项目。我这里将自动生成Class1.cs文件删除。
文章图片
首先通过Nuget添加对 Microsoft.CrmSdk.Data 的引用。
文章图片
安装时候会自动安装Microsoft.CrmSdk.CoreAssemblies ,安装完成后界面如下。
文章图片
像开发插件一样,需要为该程序集添加签名。
文章图片
然后一般至少需要添加两个类,分别为RetrieveMutiple 和 Retrieve消息准备,我这里使用的示例代码如下。值得注意的是虚拟实体的主键,比如我这里是 ly_testvirtualentityid 一定要赋值,否则会出错。
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Data.Exceptions;
using Microsoft.Xrm.Sdk.Extensions;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
namespace LuoYongCustomDataProvider
{
public class RetrieveMultiple : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService orgSvc = factory.CreateOrganizationService(context.UserId);
try
{
QueryExpression query = context.InputParameterOrDefault<
QueryExpression>
("Query");
var filter = query.Criteria;
//下面的代码可以用来查看查询使用的QueryExpression转换后的FetchXml
QueryExpressionToFetchXmlRequest req = new QueryExpressionToFetchXmlRequest();
req.Query = query;
QueryExpressionToFetchXmlResponse resp = (QueryExpressionToFetchXmlResponse)orgSvc.Execute(req);
tracingService.Trace($"Query expression to fetchxml={resp.FetchXml}");
var transferedFetchXml = resp.FetchXml.Replace("ly_testvirtualentityid", "accountid");
transferedFetchXml = transferedFetchXml.Replace("ly_testvirtualentity","account");
transferedFetchXml = transferedFetchXml.Replace("ly_name", "name");
transferedFetchXml = transferedFetchXml.Replace("ly_createdon", "createdon");
transferedFetchXml = transferedFetchXml.Replace("ly_website", "websiteurl");
tracingService.Trace($"Transfered Fetchxml={transferedFetchXml}");
EntityCollection results = new EntityCollection();
var queryResults = ExecuteQuery(tracingService, transferedFetchXml).Result;
queryResults.ForEach(t =>
{
results.Entities.Add(new Entity("ly_testvirtualentity") {
Id = t.accountid,
Attributes = {
["ly_testvirtualentityid"] = t.accountid,
["ly_name"] = t.name,
["ly_createdon"] = Convert.ToDateTime(t.createdon).ToUniversalTime(),
["ly_website"] = t.websiteurl
}
});
});
tracingService.Trace($"results.count = {results.Entities.Count}");
context.OutputParameters["BusinessEntityCollection"] = results;
}
catch (Exception e)
{
tracingService.Trace($"{e.Message} {e.StackTrace}");
if (e.InnerException != null)
tracingService.Trace($"{e.InnerException.Message} {e.InnerException.StackTrace}");
throw new InvalidPluginExecutionException(e.Message);
}
}private static async Task<
List<
Result>
>
ExecuteQuery(ITracingService tracer, string fetchXml)
{
if (string.IsNullOrEmpty(fetchXml))
{
fetchXml = @"<
fetch version=\'1.0\' mapping=\'logical\' distinct=\'false\'>
<
entity name=\'account\'>
<
attribute name=\'name\' />
<
attribute name=\'accountid\' />
<
attribute name=\'websiteurl\' />
<
attribute name=\'createdon\' />
<
order attribute=\'name\' descending=\'false\' />
<
filter type=\'and\'>
<
condition attribute=\'statecode\' operator=\'eq\' value=https://www.songbingjia.com/'0\' />
<
/filter>
<
/entity>
<
/fetch>
";
}
System.Collections.Generic.List<
KeyValuePair<
string, string>
>
vals = new System.Collections.Generic.List<
KeyValuePair<
string, string>
>
();
vals.Add(new KeyValuePair<
string, string>
("client_id", @"bd41df00-ae2b-4d94-aa12-18fb231c0b51"));
vals.Add(new KeyValuePair<
string, string>
("resource", @"https://logicalinventorycenter.crm5.dynamics.com/"));
vals.Add(new KeyValuePair<
string, string>
("grant_type", "client_credentials"));
vals.Add(new KeyValuePair<
string, string>
("client_secret", @"8Es-j~RZG~-w.1Q7D_546r5IWZIq9XkqIp"));
string tokenUrl = string.Format("https://login.windows.net/{0}/oauth2/token", @"4a54995a-f092-4738-806e-c8427bdc39fb");
using (HttpClient httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
HttpContent content = new FormUrlEncodedContent(vals);
HttpResponseMessage hrm = httpClient.PostAsync(tokenUrl, content).Result;
if (hrm.IsSuccessStatusCode)
{
string data = https://www.songbingjia.com/android/await hrm.Content.ReadAsStringAsync();
//tracer.Trace($"Get token response = {data}");
var authenticationResponse = Utility.DeserializeDictionary(data);
var request = new HttpRequestMessage(HttpMethod.Get, $"https://logicalinventorycenter.crm5.dynamics.com/api/data/v9.1/accounts?fetchXml={fetchXml}");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authenticationResponse["access_token"]);
request.Headers.Add("OData-MaxVersion", "4.0");
request.Headers.Add("OData-Version", "4.0");
var queryResponseStr = httpClient.SendAsync(request).Result.Content.ReadAsStringAsync().Result;
//tracer.Trace($"Execute query result = {queryResponseStr}");
return ((QueryResult)Utility.DeserializeObject(queryResponseStr, typeof(QueryResult))).value;
}
else
{
throw new InvalidPluginExecutionException("Get token error!");
}
}
}
}
}
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Data.Exceptions;
using Microsoft.Xrm.Sdk.Extensions;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
namespace LuoYongCustomDataProvider
{
public class Retrieve : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService orgSvc = factory.CreateOrganizationService(context.UserId);
try
{
EntityReference target = (EntityReference)context.InputParameters["Target"];
tracingService.Trace($"Target: {target.Id.ToString()}");
var queryResults = ExecuteQueryById(tracingService, target.Id).Result;
var newEntity = new Entity("ly_virtualaccount");
newEntity.Id = queryResults.accountid;
newEntity["ly_testvirtualentityid"] = queryResults.accountid;
newEntity["ly_name"] = queryResults.name;
newEntity["ly_createdon"] = Convert.ToDateTime(queryResults.createdon).ToUniversalTime();
newEntity["ly_website"] = queryResults.websiteurl;
context.OutputParameters["BusinessEntity"] = newEntity;
}
catch (Exception e)
{
tracingService.Trace($"{e.Message} {e.StackTrace}");
if (e.InnerException != null)
tracingService.Trace($"{e.InnerException.Message} {e.InnerException.StackTrace}");
throw new InvalidPluginExecutionException(e.Message);
}
}private static async Task<
Result>
ExecuteQueryById(ITracingService tracingService, Guid Id)
{
System.Collections.Generic.List<
KeyValuePair<
string, string>
>
vals = new System.Collections.Generic.List<
KeyValuePair<
string, string>
>
();
vals.Add(new KeyValuePair<
string, string>
("client_id", @"bd41df00-ae2b-4d94-aa12-18fb231c0b51"));
vals.Add(new KeyValuePair<
string, string>
("resource", @"https://logicalinventorycenter.crm5.dynamics.com/"));
vals.Add(new KeyValuePair<
string, string>
("grant_type", "client_credentials"));
vals.Add(new KeyValuePair<
string, string>
("client_secret", @"8Es-j~RZG~-w.1Q7D_546r5IWZIq9XkqIp"));
string tokenUrl = string.Format("https://login.windows.net/{0}/oauth2/token", @"4a54995a-f092-4738-806e-c8427bdc39fb");
using (HttpClient httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
HttpContent content = new FormUrlEncodedContent(vals);
HttpResponseMessage hrm = httpClient.PostAsync(tokenUrl, content).Result;
if (hrm.IsSuccessStatusCode)
{
string data = https://www.songbingjia.com/android/await hrm.Content.ReadAsStringAsync();
tracingService.Trace($"Get token response = {data}");
var authenticationResponse = Utility.DeserializeDictionary(data);
var request = new HttpRequestMessage(HttpMethod.Get, $"https://logicalinventorycenter.crm5.dynamics.com/api/data/v9.1/accounts({Id})?$select=name,websiteurl,createdon");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authenticationResponse["access_token"]);
request.Headers.Add("OData-MaxVersion", "4.0");
request.Headers.Add("OData-Version", "4.0");
var queryResponseStr = httpClient.SendAsync(request).Result.Content.ReadAsStringAsync().Result;
tracingService.Trace($"Execute query result = {queryResponseStr}");
return ((Result)Utility.DeserializeObject(queryResponseStr, typeof(Result)));
}
else
{
throw new InvalidPluginExecutionException("Get token error!");
}
}
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
namespace LuoYongCustomDataProvider
{
public class Utility
{
public static Dictionary<
string, string>
DeserializeDictionary(string josnStr)
{
Dictionary<
string, string>
returnVal = null;
if (!string.IsNullOrEmpty(josnStr))
{
DataContractJsonSerializerSettings serializerSettings = new DataContractJsonSerializerSettings
{
UseSimpleDictionaryFormat = true
};
using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(josnStr)))
{
DataContractJsonSerializer deseralizer = new DataContractJsonSerializer(typeof(Dictionary<
string, string>
), serializerSettings);
returnVal = (Dictionary<
string, string>
)deseralizer.ReadObject(ms);
}
}
return returnVal;
}public static Object DeserializeObject(string josnStr, Type type)
{
Object returnVal = null;
if (!string.IsNullOrEmpty(josnStr))
{
DataContractJsonSerializerSettings serializerSettings = new DataContractJsonSerializerSettings
{
UseSimpleDictionaryFormat = true
};
using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(josnStr)))
{
DataContractJsonSerializer deseralizer = new DataContractJsonSerializer(type);
returnVal = deseralizer.ReadObject(ms);
}
}
return returnVal;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LuoYongCustomDataProvider
{
public class Result
{
public Guid accountid { get;
set;
}public string name { get;
set;
}public string createdon { get;
set;
}public string websiteurl { get;
set;
}
}public class QueryResult
{
public List<
Result>
value { get;
set;
}
}
}
【使用自定义数据源配置虚拟实体】再需要将刚才的程序集使用插件注册工具的 Register > Register New Assembly 注册下。
文章图片
文章图片
然后需要注册一个新的Data Provider, 还是使用插件注册工具 Register > Register New Data Provider .
文章图片
我的设置如下,注意Data Source Entity选择Create New Data Source ,Assembly选择我刚才注册,Retrieve和RetrieveMultiple分别选择前面创建的Class。
文章图片
这时候会弹出新窗口,我的设置如下,然后点击 Create 按钮。
文章图片
创建成功后在Register New Data Provider界面点击 Register 按钮。
文章图片
创建完了界面如下所示:
文章图片
还没完,需要在Dynamics 365中导航到 Advanced Settings > Settings > Administration > Virtual Entity Datasources ,新建一个Data Source,选择我们前面步骤创建的Data Provider,点击OK。
文章图片
输入Name,并保存记录。
文章图片
然后就是新建的虚拟实体,选择我们新建的Data Source,保存好后发布我们去看效果。
文章图片
如果有错误,可以开启插件日志,看看具体错误内容。我这里看到效果如下:
文章图片
文章图片
我为这个虚拟实体启用了快速搜索,比如将name列设定为快速查找列,搜索也是正常。
文章图片
推荐阅读
- 9.20-9.30博客精彩回顾
- WP_Query中的get_current_user_id()未返回自定义帖子类型的数据
- 除了显示最新帖子外,如何在WordPress主页上显示每个类别的最新帖子()
- 在WordPress中安装Google跟踪代码管理器(无需编写PHP主题)
- 在短代码wordpress中插入多个短代码
- 手动将插件插入WordPress页面
- 我需要更改我的wordpress主题的添加到所需的设计
- 缩进按钮和WordPress可视编辑器中的无序列表
- 使用wordpress中的自定义SQL查询通过wp_get_nav_menu_items提高wp_posts中的查询性能