2

We are developing a system that connects to Microsoft Dynamics CRM using XRM SDK , It’s server side is built on .NET MVC, Front End Side is built upon Angularjs

We are facing a latency problem in API responses, ex. Login API needs at least 1.8 seconds to retrieve success or false response, and this API is the fastest one.

Other APIs are even slower, we tried to catch the source of this slowness in order to fix it, but no luck. is it XRM SDK, API’s or CRM itself.

If you can help .

var cols = new ColumnSet("exdyn_employeecontractid", "exdyn_contractstatus");
            var secondEntityCols = new ColumnSet("exdyn_companyagreementid", "exdyn_agreementstatus");

            var filter = new FilterExpression
            {
                Conditions =
                {
                    new ConditionExpression
                    {
                        AttributeName = "exdyn_employeeid",
                        Operator = ConditionOperator.Equal,
                        Values = {model.EmployeeId}
                    },
                    new ConditionExpression
                    {
                        AttributeName = "exdyn_contractstatus",
                        Operator = ConditionOperator.Equal,
                        Values = { 3 } // approved {2} signed
                    },

                },

                FilterOperator = LogicalOperator.And
            };
            var secondFilter = new FilterExpression
            {
                Conditions =
                {
                    new ConditionExpression
                    {
                        AttributeName = "exdyn_agreementstatus",
                        Operator = ConditionOperator.Equal,
                        Values = { 2 } // active {2}
                    }
                }
            };

            var entity = _organizationService.GetEntityCollectionWithJoin
                ("exdyn_employeecontract", "exdyn_companyagreement",
                "exdyn_companyagreementid", "exdyn_companyagreementid", JoinOperator.Inner,
                cols, secondEntityCols, filter, secondFilter).Entities.FirstOrDefault();

==============

Code Use to connect :

public class WebModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            // Register your Web API controllers.
            builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
            // Register your MVC controllers.
            builder.RegisterControllers(Assembly.GetExecutingAssembly());
            // OPTIONAL: Register web abstractions like HttpContextBase.
            builder.RegisterModule<AutofacWebTypesModule>();
            builder.RegisterFilterProvider();
            builder.Register(c => new WebConfigurationSettings()).As<IWebSettings>().SingleInstance();
            builder.Register(GetOrganizationServiceManagement).SingleInstance();
            builder.Register(GetOrganizationProxy).InstancePerRequest();
            builder.Register(c => new ExceptionHandler()).SingleInstance();

            builder.Register(c => new EmployeeService(c.Resolve<IOrganizationService>()))
                .As<IEmployeeService>()
                .EnableInterfaceInterceptors()
                .InterceptedBy(typeof(ExceptionHandler))
                .InstancePerRequest();
            builder.Register(c => new AttendanceService(c.Resolve<IOrganizationService>()))
                .As<IAttendanceService>().EnableInterfaceInterceptors()
                .InterceptedBy(typeof(ExceptionHandler))
                .InstancePerRequest();

            builder.Register(c => new ContractService(c.Resolve<IOrganizationService>()))
                .As<IContractService>().EnableInterfaceInterceptors()
                .InterceptedBy(typeof(ExceptionHandler))
                .InstancePerRequest();

            builder.Register(c => new InquiryService(c.Resolve<IOrganizationService>(), c.Resolve<ILookupService>()))
                .As<IInquiryService>().EnableInterfaceInterceptors()
                .InterceptedBy(typeof(ExceptionHandler))
                .InstancePerRequest();

            builder.Register(c => new LookupService(c.Resolve<IOrganizationService>()))
                .As<ILookupService>().EnableInterfaceInterceptors()
                .InterceptedBy(typeof(ExceptionHandler))
                .InstancePerRequest();

            builder.Register(c => new LeaveRequestService(c.Resolve<IOrganizationService>(),
                c.Resolve<IContractService>(), c.Resolve<ILookupService>()))
                .As<ILeaveRequestService>().EnableInterfaceInterceptors()
                .InterceptedBy(typeof(ExceptionHandler))
                .InstancePerRequest();


            builder.Register(c => new PurchaseRequestService(c.Resolve<IOrganizationService>(), c.Resolve<ILookupService>()))
                .As<IPurchaseRequestService>().EnableInterfaceInterceptors()
                .InterceptedBy(typeof(ExceptionHandler))
                .InstancePerRequest();

            builder.Register(c => new NotificationService(c.Resolve<IOrganizationService>()))
                .As<INotificationService>().EnableInterfaceInterceptors()
                .InterceptedBy(typeof(ExceptionHandler))
                .InstancePerRequest();
            builder.Register(c => new CacheClient())
                .As<ICacheClient>()
                .SingleInstance();
        }

        private static IServiceManagement<IOrganizationService> GetOrganizationServiceManagement(IComponentContext ctx)
        {
            var settings = ctx.Resolve<IWebSettings>();

            return ServiceConfigurationFactory.CreateManagement<IOrganizationService>(
                       new Uri(settings.OrganizationUrl));
        }

        private static IOrganizationService GetOrganizationProxy(IComponentContext ctx)
        {
            var svcMgmt = ctx.Resolve<IServiceManagement<IOrganizationService>>();
            var settings = ctx.Resolve<IWebSettings>();
            var credentials = new AuthenticationCredentials();
            credentials.ClientCredentials.UserName.UserName = settings.SystemUserName;
            credentials.ClientCredentials.UserName.Password = settings.SystemUserPassword;
            var authCredentials = svcMgmt.Authenticate(credentials);
            //return new OrganizationServiceProxy(svcMgmt, authCredentials.SecurityTokenResponse);
            return new OrganizationServiceProxy(svcMgmt, authCredentials.ClientCredentials);
        }

    }
Omarj
  • 1,151
  • 2
  • 16
  • 43
  • It is On-Premise or Online? Are you using the soap endpoint or the rest endpoint? – Sxntk Jan 24 '17 at 20:59
  • You've gotta post some code. How is SDK used? How is the connection instantiated? Connection string format? – dynamicallyCRM Jan 24 '17 at 22:32
  • Sounds about right to me. From my experience Dynamics CRM Online sucks: not scaleable, if it goes down it's down - nothing you can do about it, no access to underline SQL Server, poor performance, etc. – Serge Semenov Jan 25 '17 at 04:22
  • Let me suggest you at least to use async version of the SDK to avoid blocking threads in your ASP .NET app: https://www.nuget.org/packages/Microsoft.CrmSdk.CoreAssemblies.Async/ – Serge Semenov Jan 25 '17 at 04:23
  • @Sxntk its online , we use soap. – Omarj Jan 25 '17 at 06:32
  • @dynamicallyCRM updated – Omarj Jan 25 '17 at 08:05
  • Are you caching the response following authentication? Authenticating with CRM is a fairly slow operation (certainly over 1s for CRM online in my experience), so if you are authenticating on every call you would experience this level of performance. Caching IServiceManagement and SecurityTokenResponse and then creating a new OrganizationServiceProxy using cached data when querying gives us good performance in a MVC to CRM Online set up. – Alex S Jan 25 '17 at 10:50
  • @AlexS by caching you mean on crm ? and what do you mean by caching the response response ? – Omarj Jan 25 '17 at 11:21
  • Your MVC application executes the queries against CRM? Then create an IServiceManagement and cache in MVC app. Authenticate using IServiceManagement.Authenticate(your credentials) which returns AuthenticationCredentials, cache property SecurityTokenResponse in MVC. This is the expensive authentication step complete. Then for each query use those cached details in new OrganizationServiceProxy(cached IServiceManagement, cached SecurityTokenResponse), you will be executing the query as an authenticated user which is fast. We use a single account to access CRM from MVC. – Alex S Jan 25 '17 at 12:03
  • @Omarj please post the part of your code where you authenticate against CRM – Sxntk Jan 25 '17 at 13:49
  • @Sxntk check out my code – Omarj Jan 29 '17 at 12:56

1 Answers1

1

It's most likely CRM/APIs. You should be able to test this out with something like Fiddler to look at the requests being made and determine where the slowness is occurring. For example the following is querying a CRM online sandbox using the old version of the SDK:

var connection = CrmConnection.Parse(@"Url=https://ORG.crm.dynamics.com;Username=USERNAME;Password=PASSWORD");  
_orgService = new OrganizationService(connection);

_orgService.RetrieveMultiple(new FetchExpression(@"<fetch><entity name='account'><attribute name='accountid'/></entity></fetch>"));

Checking fiddler you'll see the following:

enter image description here

  1. There are ~4 seperate HTTP requests from the client in order to authenticate taking ~1.5 seconds (in this case it's purely an O365 user, ADFS is not being used)
  2. The final HTTP request is the actual query which is returning 5k account ids. It takes ~5 seconds to return.

If you time your code and compare to the actual HTTP requests you can see where the time is being spent, but I'd bet it will be the requests and not the SDK.

There are a handful of things you can do to try and optimize your queries:

  1. Only return the data needed
  2. Limit the amount of joins
  3. Make sure plugins are not running on RetrieveMultiple of the entity you are querying.
  4. Authenticate once and reuse the "connection". (Depending on which version of the SDK you are using, and which pieces of that SDK this can be done different ways. CrmServiceClient caches by default now, other connection strings had options like Service Configuration Instance Mode).
Matt Dearing
  • 9,286
  • 1
  • 23
  • 25