Serialize Huge amount of Query results
Problems
There are two problems in that task.
First - it's not possible to load all the elements into a memory, because of RAM limitations.
Second - it's required to implement page by page loading from the CRM.
Solution
To resolve described problems it's required to create a custom IEnumerable implementation. That class wouldn't be a full functional collection, but still can be used to control sequential access to the CRM prefiltered data.
The base of our collection is XrmEnumerator, IEnumerator implementation:
public class XrmEnumerator<T> : IEnumerator<T> where T : Entity
{
private readonly Queue<T> _collected = new Queue<T>();
private IOrganizationService _service;
private QueryExpression _query;
private string _lastCookie;
private bool _moreRecords;
public T Current { get; private set; }
object IEnumerator.Current => Current;
public XrmEnumerator(IOrganizationService service,
QueryExpression query)
{
_service = service;
_query = query;
if (query.PageInfo == null)
query.PageInfo = new PagingInfo
{
Count = 5000,
PageNumber = 1
};
FillThePack();
}
private void FillThePack()
{
var result = _service.RetrieveMultiple(_query);
_lastCookie = result.PagingCookie;
result.Entities.ToList()
.ForEach(e=>_collected.Enqueue(e.ToEntity<T>()));
_moreRecords = result.MoreRecords;
}
public void Dispose()
{
_service = null;
_query = null;
}
public bool MoveNext()
{
if(_collected.Count == 0)
{
if (!_moreRecords)
{
return false;
}
_query.PageInfo.PagingCookie = _lastCookie;
_query.PageInfo.PageNumber++;
FillThePack();
}
Current = _collected.Dequeue();
return true;
}
public void Reset()
{
_query.PageInfo.PagingCookie = string.Empty;
_query.PageInfo.PageNumber = 1;
}
}
The XrmEnumerator has a query and service objects inside and loads a next portion of data into the internal queue _collected. Values are taken from the _collected till there are some elements, if the queue is empty it takes a next portion of data from the CRM. That way it incapsulates the loading of pages and not use a lot of RAM because entities are not linked to anything in the object itself and could be released by the garbage collector.The collection uses the XrmEnumerator to get the data:
public class XrmEnumerable<T>Function Add is required by the serializers to be able to (de)serialize a collection. XrmEnumerable does not stores any data so the result of the deserialization process will be a broken empty collection, that would fail on a request. That is done by purpose, because storing the huge amount of data from can causes an OutOfMemoryExecption. The correct usage would be to create a child class of the XrmEnumerable and override an Add method e.g.:: IEnumerable <T> where T : Entity { private readonly XrmEnumerator <T> _enumerator; public XrmEnumerable() { } public XrmEnumerable(IOrganizationService service, QueryExpression query) { _enumerator = new XrmEnumerator (service, query); } public IEnumerator GetEnumerator() => _enumerator; IEnumerator IEnumerable.GetEnumerator() { return _enumerator; } public virtual void Add(T entity) { // do your code on item deserialization } }
public class XrmContactsToConsole : XrmEnumerable{ public XrmContactsToConsole() { } public XrmContactsToConsole(IOrganizationService service, QueryExpression query) : base(service, query) { } public override void Add(Contact entity) { Console.WriteLine(entity.FullName); } }
See below the sample of working with that collection:
var enumerable = new XrmContactsToConsole(service, query);
var serializer = new DataContractSerializer(typeof(XrmContactsToConsole));
using (var file = File.OpenWrite("contacts.xml"))
serializer.WriteObject(file, enumerable);
using (var file = File.OpenRead("contacts.xml"))
serializer.ReadObject(file);
The result of the execution will be the list of names on the console screen.
Try it for your functionality.
Comments
Post a Comment