Przez ostatni czas bardzo dużo pracuje na danych pobieranych z CRM za pomocą SDK – jeżeli ktoś z was pracował już z CRM to wie o czym mówię, Ci z was którzy jeszcze nie mieli okazji, to SDK udostępnia wam zbiór narzędzi jak i dllek, które ułatwiają pracę z CRM. Dla przykładu za pomocą CrmSvcUtil możemy wygenerować silnie typowane klasy, które są odpowiednikami encji w CRM.

To właśnie CrmSvcUtil wraz z Query Provider umożliwia nam pisanie zapytań LINQ by pobrać dane z serwera – mając dostęp do kontekstu wystarczy, że napiszemy takie o to zapytanie:

var x = from a in context.CreateQuery<Account>() select a.Id;

I dostaniemy z powrotem listę ID kont w CRM.

Jednak nie o tym tutaj chciałem :) Ostatnio byłem poproszony o napisanie pobierania i stronicowania danych CRM po stronie aplikacji klienckiej. Jak wiadomo, by móc zrobić stronicowanie (z numerami stron i opcją Last/First a nie tylko Next/Prev) trzeba wiedzieć ile jest rekordów w bazie danych.

Nic niby prostszego, mając wsparcie dla LINQ i IQuerable wystarczy tylko napisać:

var x = from a in context.CreateQuery<Account>() select a.Id;
var count = x.Count();

I powinniśmy uzyskać liczbę elementów w CRM.

Niestety, nasze zapytanie skończy się wyjątkiem:

error_thumb2

Oczywiście w takich wypadek dokumentacja MSDN jest jak zwykle niesamowicie przydatna – jak czegoś nie wspieramy to lepiej o tym nie piszmy #fail.

Oczywiście pierwszym moim pomysłem było rzutowanie wyniku na listę i następnie pobranie wartości Count:

var x = from a in context.CreateQuery<Account>() select a.Id;
var count = x.ToList().Count;

Jednak po co w tedy robić stronicowanie (chyba, że jako bajer), bo skoro i tak pobieramy wszystkie rekordy by tylko zrobić Count to idea stronicowania danych i pobierania ich dynamicznie traci sens – nawet jeżeli ograniczymy nasze zapytanie do zwracania jedynie Id to wciąż jest to nie opłacalne.

Oczywiście za pomocą małej modyfikacji, możemy w ogóle nie rzutować wyniku na listę i „pozostawić” go sobie jako IEnumerable:

IEnumerable<Guid> x = from a in context.CreateQuery<Account>() select a.Id;
var count = x.Count();

Ale tak jak poprzednio, to nie załatwia problemu, jedynie pokazuje, że na problem możemy natrafić innym sposobem.

Dodatkowo, zapytanie jest (przynajmniej w moim przypadku) absolutnie nie do użycia, gdyż czas oczekiwania na to by dostać informacje o liczbie elementów to aż 30 do 50 sekund. Wszystko przez to, że Count zmusza nas do ściągnięcia wszystkich rekordów (zgodnie z naszym zapytaniem) z CRM.

Takie rozwiązanie jest nie do przyjęcia. Jeżeli jeszcze uwzględnimy filtrowanie po polach to co „strona” będziemy musieli czekać te 40 sekund. Dodatkowym minusem jest to, że jeżeli mamy więcej encji niż 5000 to nigdy się o tym nie dowiemy :( albo się dowiemy za pomocą wyjątku przy Count, nie wiem, nie próbowałem jednak wiem iż istnieje domyślne ograniczenie na 5000 encji, które można modyfikować (zmiejszać lub zwiększać).

Na szczęście istnieje szybszy i lepszy sposób pobrania liczby elementów, ale o tym następnym razem.