Request a topic or
contact an Arke consultant
April 2009

Arke Systems Blog

Useful technical and business information straight from Arke.

About the author

Author Name is someone.
E-mail me Send mail

Recent comments




The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2023

Out of order CRM Annotations (Notes)

One of my import tools was having a problem where annotations were showing up sorted … oddly.  It would show for example annotations 4,5,6, and 7 followed by annotations 1, 2, and 3. 

Turning on trace shows that annotations were being selected from the database with a simple orderby createdon desc.  Sounds reasonable, sort by date, date should be uniquely ordered…

Repeat after me: CRM dates have no milliseconds. CRM dates have no milliseconds. CRMDateTime is a day + hh:mm:ss.  No milliseconds.

So, the createdon date was being truncated to the second, so any annotations that were created by my import tool during the same second all had the same value.  That leaves sql server with no particular directions for sorting them, so it picks the order it found them in, which happens to be the order they were created… so any annotation created within the same second sorts in creation order, then those are sorted in reverse creation order with other groups of annotations from other seconds. 

The solution is to put a second between annotation creations when importing if select order is going to be important.  If you don’t want to actually slow down your importing by a second every time you have a string of annotations, you can use the overriddencreatedon property to feed in adjusted-by-seconds times, but you have to count backwards because you can’t set a createdon date in the future.

Easy way:


Hard way: (make sure the clock of whatever system you are running on is synced properly)

/// <summary>
/// Creates a CRM annotation 
/// </summary>
/// <param name="subject">Up to 500 chars, will be trimmed if necessary.</param>
/// <param name="text">Up to 5000 chars, will be trimmed if necessary.</param>
/// <param name="owningentitytypename">EntityName.[entity].ToString()</param>
/// <param name="owningentityguid">GUID.</param>
/// <param name="createtimeoffsetseconds">Usually this should be 0.  Must be 0 or negative, can't create things in the future.</param>
/// <param name="previousannotations">If previousannotations is specified, new annotation will be added with a 
/// create time 1 second before the earliest createtime in "previousannotations" list.  
/// When displayed in normal reverse chronological order, they will display in the 
/// actual order added by this method - which means you will usually want to add them 
/// in reverse chronological order also.</param>
/// <returns></returns>
public static annotation addAnnotation(string subject, string text, string owningentitytypename, Guid owningentityguid, int createtimeoffsetseconds, LinkedList<annotation> previousannotations)
    annotation annote = new annotation();
    // todo: determine how to not need to hardcode 500/5000.
    annote.subject = GetTrimmedIfNeeded(subject, 500);
    annote.notetext = GetTrimmedIfNeeded(text, 5000);
    annote.objectid = new Lookup();
    annote.objectid.type = owningentitytypename;
    annote.objectid.Value = owningentityguid;
    annote.objecttypecode = new EntityNameReference();
    annote.objecttypecode.Value = owningentitytypename;
    annote.isdocument = new CrmBoolean();
    annote.isdocument.Value = false;
    DateTime when = DateTime.Now.AddSeconds(1 + createtimeoffsetseconds);
    if (previousannotations != null && previousannotations.Count > 0)
        foreach (annotation a in previousannotations)
            CrmDateTime comp = a.createdon ?? a.overriddencreatedon;
            if (comp != null)
                if (comp.UserTime.CompareTo(when) < 0)
                    when = comp.UserTime;
    annote.overriddencreatedon = CrmHelper.ConvertToCrmDateTime(when.AddSeconds(-1 + createtimeoffsetseconds));
    return annote;

public static annotation addAnnotation(string subject, string text, string owningentitytypename, Guid owningentityguid)
    return addAnnotation(subject, text, owningentitytypename, owningentityguid, 0, null);

/// <summary>
/// Overly fancy trimming function that trims text down to fit in length.
/// Tries to include a note indicating that trimming was done.  
/// Note is longer if more space is available.
/// Examples:
/// ?CrmHelper.GetTrimmedIfNeeded("01234567890123456789012345678901234567890123456789", 5)
/// "01234"
/// ?CrmHelper.GetTrimmedIfNeeded("01234567890123456789012345678901234567890123456789", 6)
/// "01234*"
/// ?CrmHelper.GetTrimmedIfNeeded("01234567890123456789012345678901234567890123456789", 11)
/// "01234567..."
/// ?CrmHelper.GetTrimmedIfNeeded("0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", 51)
/// "012345678901234567890123456789012345678901(trimmed)"
/// ?CrmHelper.GetTrimmedIfNeeded("012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", 151)
/// "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678(trimmed to 151 chars)"
/// </summary>
/// <param name="str">String to maybe trim</param>
/// <param name="length">Max length after trimming, including trim note (if any)</param>
/// <returns>String no longer than length, maybe trimmed, maybe with a note indicating the trimming was done.</returns>
public static string GetTrimmedIfNeeded(string str, int length)
    if (str.Length > length)
        string trimmedwarning="";
        if (length > 150) {
            trimmedwarning="(trimmed to " + length + " chars)";
        } else if (length > 50) {
        } else if (length > 10) {
        } else if (length > 5) {
        int warninglength = trimmedwarning.Length;

        return str.Substring(0, (length-warninglength)) + trimmedwarning;
        return str;

Posted by David Eison on Friday, April 10, 2009 3:23 AM
Permalink | Comments (0) | Post RSSRSS comment feed