一种提升连接Dynamics 365性能的方法

关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复256或者20170512可方便获取本文,同时可以在第一间得到我发布的最新的博文信息,follow me!我的网站是 www.luoyong.me 。
 
最近做代码审查,有个建议是Improve Microsoft Dynamics CRM service channel allocation performance的建议,请教了微软专家,同事也参考了官方的文章:Best practices for developing with Dynamics 365 for Customer Engagement ,Get了一下新技能,下面我做一次传声筒和翻译器,请听我讲来。
为了更好的看到效果,我这里使用了多个线程来凸显效果。我以前的项目,不在Dynamics 365中使用组织服务的话,一般都是每次查询会建立一次连接,代码类似如下:
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Tooling.Connector;
using System;
using System.ServiceModel;
using System.Threading;

namespace LuoYongLab
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                for (var i = 0; i < 5; i++)
                {
                    ThreadStart tStart = new ThreadStart(Work);
                    Thread thread = new Thread(tStart);
                    thread.Start();
                }
                Console.WriteLine("程序运行完成!");
                Console.ReadKey();
            }
            catch (FaultException ex)
            {
                Console.WriteLine("程序出现异常:ex.Message=" + ex.Message);
                Console.ReadKey();
            }
        }

        static void Work()
        {
            Console.WriteLine("线程开始" + DateTime.Now.ToLongTimeString() + ";线程ID:" + Thread.CurrentThread.ManagedThreadId);
            var crmSvc = new CrmServiceClient(new System.Net.NetworkCredential("crmadmin@luoyong.me", "Pass", null), Microsoft.Xrm.Tooling.Connector.AuthenticationType.IFD, "demo.luoyong.me", "443", "demo", useUniqueInstance: false, useSsl: true);
            Console.WriteLine("线程ID: " + Thread.CurrentThread.ManagedThreadId + ";Token过期时间:" + crmSvc.OrganizationServiceProxy.SecurityTokenResponse.Response.Lifetime.Expires);
            if (crmSvc.IsReady)
            {
                QueryExpression qe = new QueryExpression("organization");
                qe.ColumnSet = new ColumnSet("languagecode", "basecurrencyid");
                EntityCollection ec = crmSvc.RetrieveMultiple(qe);
                if (ec.Entities.Count >= 1)
                {
                    Console.WriteLine("线程ID: " + Thread.CurrentThread.ManagedThreadId + ";组织偏好语言:" + ec.Entities[0].GetAttributeValue<int>("languagecode"));
                }
            }
            Console.WriteLine("线程结束" + DateTime.Now.ToLongTimeString() + ";线程ID:" + Thread.CurrentThread.ManagedThreadId);
        }
    }
}
效果如下,可以看到大概需要7秒钟。
 
我用fiddler抓包,你会发现每次认证都会下载元数据,一共下载了5次。
 
如果我改动代码如下,类 ManagedTokenOrganizationServiceProxy 代码来自Ali Sharifi 的文章 REFRESH SECURITY TOKEN FOR MICROSOFT DYNAMICS CRM CONNECTION ,但是我发现没什么用,因为 this._proxy.SecurityTokenResponse == null,所以我这里不会使用。
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Configuration;
using System.ServiceModel;
using System.Threading;

namespace ConsoleApp
{
    class Program
    {
        public static IServiceManagement<IOrganizationService> sm;
        public static AuthenticationCredentials authCredentials;
        static void Main(string[] args)
        {
            sm = ServiceConfigurationFactory.CreateManagement<IOrganizationService>(new Uri(ConfigurationManager.AppSettings["orgUrl"]));
            authCredentials = new AuthenticationCredentials();
            authCredentials.ClientCredentials.UserName.UserName = ConfigurationManager.AppSettings["userName"];
            authCredentials.ClientCredentials.UserName.Password = ConfigurationManager.AppSettings["passWord"];
            authCredentials = sm.Authenticate(authCredentials);
            try
            {
                for (var i = 0; i < 5; i++)
                {
                    ThreadStart tStart = new ThreadStart(Work);
                    Thread thread = new Thread(tStart);
                    thread.Start();
                }
                Console.WriteLine("程序运行完成!");
                Console.ReadKey();
            }
            catch (FaultException ex)
            {
                Console.WriteLine("程序出现异常:ex.Message=" + ex.Message);
                Console.ReadKey();
            }
        }

        static void Work()
        {
            Console.WriteLine("线程开始" + DateTime.Now.ToLongTimeString() + ";线程ID:" + Thread.CurrentThread.ManagedThreadId);
            OrganizationServiceProxy orgSvc = new OrganizationServiceProxy(sm, authCredentials.ClientCredentials);
            //OrganizationServiceProxy orgSvc = new OrganizationServiceProxy(sm, authCredentials.SecurityTokenResponse);
            //ManagedTokenOrganizationServiceProxy orgSvc = new ManagedTokenOrganizationServiceProxy(sm, authCredentials.ClientCredentials);
            QueryExpression qe = new QueryExpression("organization");
            qe.ColumnSet = new ColumnSet("languagecode", "basecurrencyid");
            EntityCollection ec = orgSvc.RetrieveMultiple(qe);
            if (ec.Entities.Count >= 1)
            {
                Console.WriteLine("线程ID: " + Thread.CurrentThread.ManagedThreadId + ";组织偏好语言:" + ec.Entities[0].GetAttributeValue<int>("languagecode"));
            }
            Console.WriteLine("线程结束" + DateTime.Now.ToLongTimeString() + ";线程ID:" + Thread.CurrentThread.ManagedThreadId);
        }
    }
}
 
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using System;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace ConsoleApp
{

    public sealed class ManagedTokenOrganizationServiceProxy : OrganizationServiceProxy
    {
        private AutoRefreshSecurityToken<OrganizationServiceProxy, IOrganizationService> _proxyManager;

        public ManagedTokenOrganizationServiceProxy(Uri serviceUri, ClientCredentials userCredentials)
            : base(serviceUri, null, userCredentials, null)
        {
            this._proxyManager = new AutoRefreshSecurityToken<OrganizationServiceProxy, IOrganizationService>(this);
        }

        public ManagedTokenOrganizationServiceProxy(IServiceManagement<IOrganizationService> serviceManagement,
            SecurityTokenResponse securityTokenRes)
            : base(serviceManagement, securityTokenRes)
        {
            this._proxyManager = new AutoRefreshSecurityToken<OrganizationServiceProxy, IOrganizationService>(this);
        }

        public ManagedTokenOrganizationServiceProxy(IServiceManagement<IOrganizationService> serviceManagement,
            ClientCredentials userCredentials)
            : base(serviceManagement, userCredentials)
        {
            this._proxyManager = new AutoRefreshSecurityToken<OrganizationServiceProxy, IOrganizationService>(this);
        }

        protected override void AuthenticateCore()
        {
            this._proxyManager.PrepareCredentials();
            base.AuthenticateCore();
        }

        protected override void ValidateAuthentication()
        {
            this._proxyManager.RenewTokenIfRequired();
            base.ValidateAuthentication();
        }
    }

    ///<summary>
    /// Class that wraps acquiring the security token for a service
    /// </summary>

    public sealed class AutoRefreshSecurityToken<TProxy, TService>
        where TProxy : ServiceProxy<TService>
        where TService : class
    {
        private TProxy _proxy;

        ///<summary>
        /// Instantiates an instance of the proxy class
        /// </summary>

        /// <param name="proxy">Proxy that will be used to authenticate the user</param>
        public AutoRefreshSecurityToken(TProxy proxy)
        {
            if (null == proxy)
            {
                throw new ArgumentNullException("proxy");
            }

            this._proxy = proxy;
        }

        ///<summary>
        /// Prepares authentication before authenticated
        /// </summary>

        public void PrepareCredentials()
        {
            if (null == this._proxy.ClientCredentials)
            {
                return;
            }

            switch (this._proxy.ServiceConfiguration.AuthenticationType)
            {
                case AuthenticationProviderType.ActiveDirectory:
                    this._proxy.ClientCredentials.UserName.UserName = null;
                    this._proxy.ClientCredentials.UserName.Password = null;
                    break;
                case AuthenticationProviderType.Federation:
                case AuthenticationProviderType.LiveId:
                    this._proxy.ClientCredentials.Windows.ClientCredential = null;
                    break;
                default:
                    return;
            }
        }

        ///<summary>
        /// Renews the token (if it is near expiration or has expired)
        /// </summary>

        public void RenewTokenIfRequired()
        {
            if (null != this._proxy.SecurityTokenResponse &&
            DateTime.UtcNow.AddMinutes(15) >= this._proxy.SecurityTokenResponse.Response.Lifetime.Expires)
            {
                try
                {
                    this._proxy.Authenticate();
                }
                catch (CommunicationException)
                {
                    if (null == this._proxy.SecurityTokenResponse ||
                        DateTime.UtcNow >= this._proxy.SecurityTokenResponse.Response.Lifetime.Expires)
                    {
                        throw;
                    }
                }
            }
        }
    }
}
我们可以看到执行时间也会缩短,从之前的大概需要7秒降低到需要1秒,速度提升不少。
 
我们看看Fiddler抓包效果,仅仅只有一次下载元数据,而且因为缓存执行的非常快。
 
如果要缓存的话,我这里在Application Start事件时候缓存代码如下,当然要添加对 System.Runtime.Caching 的引用。
using System.Runtime.Caching;            
ObjectCache cache = MemoryCache.Default; CacheItemPolicy policy = new CacheItemPolicy(); policy.Priority = CacheItemPriority.NotRemovable; IServiceManagement<IOrganizationService> sm = ServiceConfigurationFactory.CreateManagement<IOrganizationService>(new Uri(ConfigurationManager.AppSettings["orgUrl"])); cache.Set("sm", sm, policy); AuthenticationCredentials authCredentials = new AuthenticationCredentials(); authCredentials.ClientCredentials.UserName.UserName = ConfigurationManager.AppSettings["userName"]; authCredentials.ClientCredentials.UserName.Password = ConfigurationManager.AppSettings["passWord"]; cache.Set("authCredentials", sm.Authenticate(authCredentials), policy);

然后在代码中获取组织服务就用如下代码:

using System.Runtime.Caching;
ObjectCache cache = MemoryCache.Default; OrganizationServiceProxy orgSvc = new OrganizationServiceProxy(((IServiceManagement<IOrganizationService>)cache["sm"]), ((AuthenticationCredentials)cache["authCredentials"]).ClientCredentials);
 
原文地址:https://www.cnblogs.com/luoyong0201/p/Dynamics_365_High_Performance_Connection_Method.html