Добро пожаловать в мой блог! Изначально он не задумывался как блог CRM разработчика, но жизнь сама внесла нужные коррективы. Тут я публикою все свои наблюдения относительно обозначенных в заголовке систем. Если Вы найдете в нем что-то интересное для Вас, как для заказчика, то буду рад сотрудничать с Вами! В моей компетенции 100% задач по MS CRM 3.0/4.0/2011:
MVP 2010, 2011
- Консалтинг
- Проектирование
- Разработка
- Обучение
MVP 2010, 2011
Особенности выборки по Id и ObjectId в OrganizationSericeContext
Запись от Артем Enot Грунин размещена 28.10.2016 в 09:39
Недавно я (впрочем не я первый) обнаружил интересный баг при работе с "контекстом" в стандартной клиентской сборке CRM. Представители "старой школы", такие как я, не привыкли пользоваться подобными инструментами, однако использование LINQ синтаксиса запросов несомненно востребовано современными программистами.
В чем, собственно суть? Предположим, вы хотите получить только идентификаторы записей, которые удовлетворяют некоторому условию. Упрощенно, ваш запрос будет выглядеть примерно вот так
Сюрпризом будет вывод программы. Было вычитано 57 атрибутов! Иными словами, все атрибуты, а не только те что я заказывал.
При использовании классического подхода к выборке данных из системы посредством QueryBase, мы в обязательном порядке должны указать ожидаемый набор атрибутов в CollumnSet. Тем не менее, система имеет свои представления касательно того что будет возвращено клиенту. В наборе атрибутов каждой запрошенной записи, в обязательном порядке будет ее идентификатор (и ряд других системных полей), например, accountid, но не будет всех полей, значения которых оказались пустыми. Важно понимать, что OrganizationSericeContext не создает под собой чего-то принципиально нового: наш LINQ запрос будет транслирован LINQ-провайдером в стандартный запрос-наследник QueryBase (по слухам генерируется Fetch-запрос). Однако, он, почему-то генерирует не пустой CollumnSet а CollumnSet(true), который вынужден вернуть все атрибуты.
Идем дальше! Давайте попробуем использовать немного другой запрос:
В результате мы получим 1 атрибут, как и договаривались. Для генерации классов раннего связывания использовалась стандартная утилита CrmSvcUtil 7.1.0001.3108 (4.0.30319.42000 – версия указанная в начале файла с кодом). При этом, если мы посмотрим на определение свойств AccountId и Id в генерированном коде, мы не увидим значимых отличий:
accountid
Оба свойства маппированы на один и тот же атрибут, следовательно должны транслироваться в одинаковые запросы при преобразовании лямбда выражения в Fetch запрос. Тем не менее, разница на лицо.
Попробовав все возможные комбинации Id и AccountId в запросе, мы получим следующий вывод программы:
Иными словами, важно чтобы в правой части выражения всегда был AccountId.
Мне не удалось получить какой-либо внятный ответ от производственной группы посредством возможностей MVP канала. Однако выяснилось, что проблема известная, хотя и не критическая: http://crmtipoftheday.com/2014/09/09/bad-id-bad/.
Второй интересный момент. Вы всегда должны заказывать AccountId в выражении select, иначе это свойство не будет инициализировано в записи Account. Технически, его значение будет где-то там - внутри контекста, но использовать его вы не сможете.
Предупрежден - значит вооружен. Не жалейте буквы и пишите полные названия идентификаторов!
В чем, собственно суть? Предположим, вы хотите получить только идентификаторы записей, которые удовлетворяют некоторому условию. Упрощенно, ваш запрос будет выглядеть примерно вот так
X++:
using (OrganizationServiceContext context = new OrganizationServiceContext(proxy)) { var account = context.CreateQuery<Account>() .Where(a => a.Name != null) .Select(a => new Account() { Id = a.Id }).FirstOrDefault(); Console.Out.WriteLine("Id = a.Id {0}", account.Attributes.Count); }
При использовании классического подхода к выборке данных из системы посредством QueryBase, мы в обязательном порядке должны указать ожидаемый набор атрибутов в CollumnSet. Тем не менее, система имеет свои представления касательно того что будет возвращено клиенту. В наборе атрибутов каждой запрошенной записи, в обязательном порядке будет ее идентификатор (и ряд других системных полей), например, accountid, но не будет всех полей, значения которых оказались пустыми. Важно понимать, что OrganizationSericeContext не создает под собой чего-то принципиально нового: наш LINQ запрос будет транслирован LINQ-провайдером в стандартный запрос-наследник QueryBase (по слухам генерируется Fetch-запрос). Однако, он, почему-то генерирует не пустой CollumnSet а CollumnSet(true), который вынужден вернуть все атрибуты.
Идем дальше! Давайте попробуем использовать немного другой запрос:
X++:
using (OrganizationServiceContext context = new OrganizationServiceContext(proxy)) { var account = context.CreateQuery<Account>() .Where(a => a.Name != null) .Select(a => new Account() { AccountId = AccountId }).FirstOrDefault(); Console.Out.WriteLine("Id = a.AccountId.Value {0}", account.Attributes.Count); }
accountid
X++:
[Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("accountid")] public System.Nullable<System.Guid> AccountId [Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("accountid")] public override System.Guid Id
Попробовав все возможные комбинации Id и AccountId в запросе, мы получим следующий вывод программы:
X++:
AccountId = a.Id 57 Id = a.Id 57 Id = a.AccountId.Value 1 AccountId = a.AccountId 1
Мне не удалось получить какой-либо внятный ответ от производственной группы посредством возможностей MVP канала. Однако выяснилось, что проблема известная, хотя и не критическая: http://crmtipoftheday.com/2014/09/09/bad-id-bad/.
Второй интересный момент. Вы всегда должны заказывать AccountId в выражении select, иначе это свойство не будет инициализировано в записи Account. Технически, его значение будет где-то там - внутри контекста, но использовать его вы не сможете.
Предупрежден - значит вооружен. Не жалейте буквы и пишите полные названия идентификаторов!
Всего комментариев 0