AXForum  
Вернуться   AXForum > Блоги > CRM, SharePoint и Черная Магия
All
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск

Добро пожаловать в мой блог! Изначально он не задумывался как блог CRM разработчика, но жизнь сама внесла нужные коррективы. Тут я публикою все свои наблюдения относительно обозначенных в заголовке систем. Если Вы найдете в нем что-то интересное для Вас, как для заказчика, то буду рад сотрудничать с Вами! В моей компетенции 100% задач по MS CRM 3.0/4.0/2011:
  • Консалтинг
  • Проектирование
  • Разработка
  • Обучение


MVP 2010, 2011
Оценить эту запись

Представления с использованием API

Запись от Артем Enot Грунин размещена 03.09.2010 в 09:05
Теги fetch, savedquery

Многие, наверно, замечали, что в системе содержится ряд представлений, которые не возможно построить при помощи расширенного поиска, например, "организации, без заказов за последние 6 месяцев". При помощи SDK такой запрос построить тоже не возможно, но речь сейчас не о том. Очевидно, что в системе реализован альтернативный (без использования fetch) механизм построения представлений, который мог бы быть полезен разработчику: Query API. Представления хранятся в системе в форме двух объектов SavedQuery - представления которыми владеет организация и UserQuery - пользовательские представления. Оба объекта имеют атрибут fetchxml который реализует доступный нам метод построения представлений. SavedQuery так же имеет атрибут queryapi который используется системой для выполнения сложных запросов, которые невозможно построить используется язык fetch. К сожалению, список доступных queryapi жестко прошит в системе и не поддается расширению законными способами. И тем не менее выход есть! Идея состоит в том, чтобы написать плагин на событие preRetrive сущностей SavedQuery или UserQuery, где любым удобным для нас способом получить список записей аналитического представления, после чего составить fetch запрос содержащий буквальную выборку этих записей и поместить его вместо оригинального значения атрибута fetchxml. В случае, если запрос можно построить через объектную модель CRM его можно просто конвертировать в fetch при помощи запроса QueryExpressionToFetchXmlRequest. В противном случае мы вынуждены использовать SQL. Для идентификации представлений которым нужно "помочь" построить запрос я решил использовать не название, которое может менять в зависимости от требований заказчика, а поле "описание", которое большинство пользователей никогда не увидит. В результате родился такой вот код:
Код:
using System;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using System.Data.SqlClient;
using Microsoft.Crm.Sdk.Query;

namespace CustomQueryAPI
{
    public class QueryFetchPlugin : IPlugin
    {
        private string connectionString;
        IPluginExecutionContext pluginContext;

        public QueryFetchPlugin(string unsecure, string secure)
        {
            //"Data Source=CRMDEV;Initial Catalog=ICS_MSCRM;Integrated Security=SSPI"
            connectionString = secure;
        }

        public void Execute(IPluginExecutionContext context)
        {
            this.pluginContext = context;

            DynamicEntity query = (DynamicEntity)pluginContext.OutputParameters[ParameterName.BusinessEntity];
            string api = (string)query.Properties["description"];
            if (String.IsNullOrEmpty(api)) return;

            // Для систематизации и удобства наращивания api группируются в наборы ClassAPI.MetodAPI
            string[] paths = api.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries);
            if (paths.Length < 2) return;



            string className = paths[0];
            string methodName = paths[1];

            string myFetch = String.Empty;

            switch (className)
            {
                case "Contact":
                    myFetch = retrieveContact(methodName);
                    break;
                case "Account":
                    myFetch = retrieveAccount(methodName);
                    break;
            }

            if (myFetch != null) query.Properties["fetchxml"] = myFetch;
        }

        private string queryExpressionToFetch(QueryBase query)
        {
            QueryExpressionToFetchXmlRequest request = new QueryExpressionToFetchXmlRequest();
            request.Query = query;

            ICrmService service = this.pluginContext.CreateCrmService(true);
            QueryExpressionToFetchXmlResponse responce = (QueryExpressionToFetchXmlResponse)service.Execute(request);

            return responce.FetchXml;
        }


        private string sqlToFetch(string entity, string primarykey, string sql)
        {
            string fetchPattern = @"<fetch version=""1.0"" output-format=""xml-platform"" mapping=""logical"" distinct=""false"">
                                            <entity name=""{0}"">
                                                <attribute name=""{1}""/>
                                                <filter type=""and"">
                                                    <condition attribute=""{1}"" operator=""in"">
                                                            {2}
                                                    </condition>
                                                </filter>
                                            </entity>
                                    </fetch>";

            string filterValues = String.Empty;

            using (SqlConnection sqlConnection = new SqlConnection(this.connectionString))
            {
                sqlConnection.Open();
                SqlCommand cmd = new SqlCommand(sql, sqlConnection);

                SqlDataReader reader = cmd.ExecuteReader();
                try
                {
                    while (reader.Read())
                    {
                        string id = ((Guid)reader[0]).ToString();
                        filterValues += "<value>" + id + "</value>\n";
                    }
                }
                finally
                {
                    reader.Close();
                }

            }
            return String.Format(fetchPattern, entity, primarykey, filterValues);
        }

        private string retrieveAccount(string methodName)
        {
            string fetch = String.Empty;
            switch (methodName)
            {
                case "NoOpportunitys":
                    string sqlQuery = @"select account.accountid from account
                                        left outer join opportunity
                                        on account.accountid = opportunity.customerid
                                        where   (opportunity.OpportunityId is null)";

                    fetch = sqlToFetch("account", "accountid", sqlQuery);
                    break;
            }

            return fetch;
        }

        private string retrieveContact(string methodName)
        {
            string fetch = String.Empty;
            switch (methodName)
            {
                case "BDayThisMonth":
                    string sqlQuery = "SELECT Contactid FROM Contact WHERE MONTH(GETDATE()) = MONTH(BirthDate)";

                    fetch = sqlToFetch("contact", "contactid", sqlQuery);
                    break;
            }
            return fetch;
        }

    }
}
Так как запрос представлений происходит постоянно, с целью повышения производительности решения параметры подключения к базе я передаю через конструктор плагина.
Так же мы вынуждены использовать не фильтрованные представления, так как учетка под которой работает пул (у меня это сетевая служба) может не иметь к ним доступа. И последний момент: в использовании SQL может быть скрытый подлог безопасности: если у пользователя нет прав на просмотр каких то записей, то fetch их и не вернет - это нормально. Однако, представим ситуацию, когда пользователь не имеет права на просмотр заказов, но видит все организации. Если ваш запрос возвращает все организации с заказами на сумму больше миллиона, то пользователь, не имея прав на аналитику по заказам, косвенно сможет оценить их стоимость на основании данных по полученным организациям.
Размещено в CRM
Просмотров 26769 Комментарии 1
Всего комментариев 1

Комментарии

  1. Старый комментарий
    Аватар для Артем Enot Грунин
    Спасибо Konstantin Katsovich за замечание, возможно я опечатался и плагин нужно писать на пост ретрив, как будет возможность проверю и отпишу резюме.
    Запись от Артем Enot Грунин размещена 21.04.2011 в 23:56 Артем Enot Грунин is offline
 


Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 21:45.