• 使用自定义数据源配置虚拟实体


    我是微软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='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 = 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 = 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列设定为快速查找列,搜索也是正常。

  • 相关阅读:
    [LeetCode] Walls and Gates
    [LeetCode] Expression Add Operators
    [LeetCode] Inorder Successor in BST
    [LeetCode] Peeking Iterator
    [CTCI] 最小调整有序
    [LeetCode] Single Number III
    [LeetCode] Zigzag Iterator
    [LeetCode] Wiggle Sort
    android 设颜色透明值
    android android BitmapFactory报错OOM
  • 原文地址:https://www.cnblogs.com/luoyong0201/p/Dynamics_365_Develop_Virtual_Entity_with_Custom_Data_Provider.html
Copyright © 2020-2023  润新知