Share via


(Nearly) complete MS-CRM entity serialization sample

[I’m finally getting around to formatting this thing so it’s readable and changing the broken links]

This example assumes that you've created correct XSD from the MS-CRM metadata (see https://blogs.msdn.com/mikemill/archive/2004/12/01/273254.aspx) and that you've run that XSD through the XSD Object Generator. The one change you should making prior to creating classes is to change the tns:dateTimeType to derive from xsd:string instead of xsd:dateTime, otherwise you'll end up serializing dates that the platform can't really cope with.

With a few simple tweaks to the XSD generator you can create XSD that the VS XSD.EXE tool can use. The way I've done this is to change the generated xsd:complexType to be the entity name followed by 'Type' and to create xsd:elements that reference the complexType.

(Apologies in advance if the formatting here is off, I'm still working my way around the .Text editor).

using System;

using System.Text;

using System.Web.Services;

using System.Runtime.Serialization;

using System.Xml.Serialization;

using System.IO;

using System.Globalization;

using Microsoft.Crm.Entities;

using Microsoft.Crm.Platform.Proxy;

namespace Cheerios

{

    class SerializerSample

    {

        /// <summary>

        /// Returns a string (XML) representation of a serializable

        /// entity. The namespaces are removed because the CRM

        /// platform deserialization code won't cope welll with them.

        /// </summary>

        /// <param name="o">The instance to serialize</param>

        /// <returns></returns>

        public static string ToString(object o)

        {

            StringBuilder sb = new StringBuilder();

            XmlSerializer serializer = new XmlSerializer(o.GetType());

            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();

            ns.Add("", ""); TextWriter writer = new StringWriter(sb);

            serializer.Serialize(writer, o, ns); return sb.ToString();

        }

        /// <summary>

        /// Returns an instance of a object created from the supplied

        /// XML. You must cast the return value to the correct class.

        /// </summary>

        /// <param name="t"></param>

        /// <param name="s"></param>

        /// <returns></returns>

        public static object ToObject(Type t, string s)

        {

            XmlSerializer serializer = new XmlSerializer(t);

            TextReader reader = new StringReader(s);

            return serializer.Deserialize(reader);

        }

        /// <summary> /// The main entry point for the application.

        /// </summary>

        [STAThread]

        static void Main(string[] args)

        {

            // create the service proxies

            BizUser userService = new BizUser();

            userService.Url = "https://paint/mscrmservices/bizuser.srf";

            userService.Credentials = System.Net.CredentialCache.DefaultCredentials;

           

            CRMContact contactService = new CRMContact();

            contactService.Url = "https://paint/mscrmservices/crmcontact.srf";

            contactService.Credentials = System.Net.CredentialCache.DefaultCredentials;

            // get our credentials from the platform

            CUserAuth userAuth = userService.WhoAmI();

            // create an contact object in "client-space" and set some properties

            contact theContact = new contact();

            // the CRM platform wants to see dates like YYYY-MM-DDTHH:MM:SS

            theContact.birthdate.Value =

                DateTime.UtcNow.ToString(CultureInfo.InvariantCulture.DateTimeFormat.SortableDateTimePattern);

            // set some other properties on the object

            theContact.customertypecode.Value = 6;

            theContact.creditlimit.Value = 123456.0F;

            theContact.creditonhold.Value = true;

            theContact.firstname = "Bob";

           theContact.lastname = "Jones";

            theContact.description = "This is my sample contact....";

            // set up the ownership information because the platform won't

            // automatically do that for you

            theContact.ownerid.Value = userAuth.UserId;

            theContact.ownerid.type = 8;

            // turn it into XML

            string initialContactXml = SerializerSample.ToString(theContact);

            Console.WriteLine("Created contact XML\n{0}\n", initialContactXml);

            // stuff in into the platform and get the new one back

            string updatedContactXml =

                contactService.CreateAndRetrieve(userAuth, initialContactXml);

            Console.WriteLine("Retrieved contact XML\n{0}\n", updatedContactXml);

            // turn the new one into an object

            theContact = (contact)ToObject(typeof(contact), updatedContactXml);

            Console.WriteLine("Serialized new contact to\n{0}\n", SerializerSample.ToString(theContact));

            Console.WriteLine("Hit <return> to continue"); Console.ReadLine();

        }

    }

}

The XML generated from the first serialization (from theContact to initialContactXml) looks like the following. This string can be passed directly to the MS-CRM platform as the ContactXml parameter.

<?xml version="1.0" encoding="utf-16"?>

<contact>

    <birthdate>2004-09-10T16:40:54</birthdate>

    <creditlimit>123456</creditlimit>

    <creditonhold>true</creditonhold>

    <customertypecode>6</customertypecode>

    <description>This is my sample contact....</description>

    <firstname>Bob</firstname>

    <lastname>Jones</lastname>

    <ownerid type="8">{9CBA0E1D-882F-4A39-9441-B4FA3BA14724}</ownerid>

</contact>

The call to CreateAndRetrieve returns the following XML (note, I've formatted this a bit so that it's readable). Notice the date, boolean, and lookup elements have extra XML attributes automatically. These will be mapped to corresponding values in the supporting C# types. One thing to notice here is that the platform XML doesn't include the processing instruction. You can assume that the platform will return UTF-8 formatted XML.

<?xml version="1.0" encoding="utf-16"?>

<contact>

    <contactid>{CD303B40-B8E5-4B84-ABFF-A2DA4C6D9E35}</contactid>

    <customertypecode>6</customertypecode>

    <owningbusinessunit>{7DE28647-59CA-4C95-B7A3-FA7E31CD6DA8}</owningbusinessunit>

    <participatesinworkflow>0</participatesinworkflow>

    <firstname>Bob</firstname>

    <lastname>Jones</lastname>

    <fullname>Jones, Bob</fullname>

    <birthdate date="09/10/2004" time="4:40 PM">2004-09-10T16:40:54-07:00</birthdate>

    <description>This is my sample contact....</description>

    <creditlimit>123456</creditlimit>

    <createdon date="09/10/2004" time="9:40 AM">2004-09-10T09:40:32-07:00</createdon>

    <creditonhold name="Yes">1</creditonhold>

    <createdby name="Miller, Michaeljon" dsc="0">{9CBA0E1D-882F-4A39-9441-B4FA3BA14724}</createdby>

    <modifiedon date="09/10/2004" time="9:40 AM">2004-09-10T09:40:32-07:00</modifiedon>

    <modifiedby name="Miller, Michaeljon" dsc="0">{9CBA0E1D-882F-4A39-9441-B4FA3BA14724}</modifiedby>

    <statecode name="Active">0</statecode>

    <statuscode name="Active">1</statuscode>

    <address1_addressid>{B315C1B1-ACFF-4438-97B5-FF9EA34A681F}</address1_addressid>

    <address2_addressid>{80FB9B47-4F68-44B1-A619-402F4AD94061}</address2_addressid>

    <ownerid name="Miller, Michaeljon" dsc="0" type="8">{9CBA0E1D-882F-4A39-9441-B4FA3BA14724}</ownerid>

</contact>

Finally we can take the returned platform XML and convert it into a class. For this example I'm simply turning the class back into an XML string, but you can use that resulting object like you would any object. It's also quite possible to create collection classes and use the bulk retrieve operations to hold collections of CRM "objects".

<?xml version="1.0" encoding="utf-16"?>

<contact>

    <address1_addressid>{2025B5AA-BAD7-4816-9406-BEF62BA89358}</address1_addressid>

    <address2_addressid>{5135ECBE-56E3-41A7-99F8-7ACC3C3B9FDA}</address2_addressid>

    <birthdate date="09/10/2004" time="4:50 PM">2004-09-10T16:50:08-07:00</birthdate>

    <contactid>{012014CF-350C-42CE-90D1-533A222CEE0A}</contactid>

    <createdby name="Miller, Michaeljon" dsc="0">{9CBA0E1D-882F-4A39-9441-B4FA3BA14724}</createdby>

    <createdon date="09/10/2004" time="9:49 AM">2004-09-10T09:49:55-07:00</createdon>

    <creditlimit>123456</creditlimit>

    <creditonhold name="Yes">true</creditonhold>

    <customertypecode>6</customertypecode>

    <description>This is my sample contact....</description>

    <firstname>Bob</firstname>

    <fullname>Jones, Bob</fullname>

    <lastname>Jones</lastname>

    <modifiedby name="Miller, Michaeljon" dsc="0">{9CBA0E1D-882F-4A39-9441-B4FA3BA14724}</modifiedby>

    <modifiedon date="09/10/2004" time="9:49 AM">2004-09-10T09:49:55-07:00</modifiedon>

    <ownerid name="Miller, Michaeljon" type="8" dsc="0">{9CBA0E1D-882F-4A39-9441-B4FA3BA14724}</ownerid>

    <owningbusinessunit>{7DE28647-59CA-4C95-B7A3-FA7E31CD6DA8}</owningbusinessunit>

    <participatesinworkflow>false</participatesinworkflow>

    <statecode name="Active">0</statecode>

    <statuscode name="Active">1</statuscode>

</contact>

I hope this helps developers better understand the relationship between the MS-CRM XML and .Net classes.

Comments

  • Anonymous
    October 08, 2004
    Your tools and posts got me very close to a full set of CRM types. However, I needed to make a couple tweaks to the schema-generating SQL script.

    A couple of changes I found useful:
    1. eliminate namespaces from XSD to eliminate runtime deserialization exception
    2. declare singular types as elements rather than complex types to make XSD.exe work with them
    3. use no types other than xs:string otherwise XmlSerializer can't properly reflect the types and uninitialized values don't act the same way CRM XML does
    4. order output by Attribute.ColumnNumber to get xsd:sequence to match CRM serialization behavior

    My changes to your procedure look like this:

    <font color="red">
    set nocount on

    declare @view table (
    idvalue int identity,
    value nvarchar(4000)
    )

    declare @attributeName nvarchar(50)
    declare @typeName nvarchar(50)
    declare @entityName nvarchar(50)

    declare @buildDate datetime
    declare @buildNumber nvarchar(20)

    select @buildDate = coalesce(BuildDate, getutcdate()),
    @buildNumber = cast(coalesce(MajorVersion, 1) as nvarchar) + '.' + cast(coalesce(MinorVersion, 0) as nvarchar) + '.' + cast(coalesce(BuildNumber, 0) as nvarchar)
    from BuildVersion

    declare entityCursor cursor for
    select LogicalName
    from Entity
    where IsIntersect = 0
    and IsSecurityIntersect = 0
    and IsLookupTable = 0
    and IsAssignment = 0
    and LogicalName not like '%activity%'
    and LogicalName != 'activitypointer'
    order by 1

    -- write the top-level schema tags and namespace information
    insert @view (value) values ('<?xml version="1.0" encoding="utf-8" ?>')

    insert @view (value) values ('<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">')
    --insert @view (value) values (' targetNamespace="http://www.microsoft.com/mbs/crm/schemas/2004"">http://www.microsoft.com/mbs/crm/schemas/2004"')
    --insert @view (value) values (' xmlns:tns="http://www.microsoft.com/mbs/crm/schemas/2004"">http://www.microsoft.com/mbs/crm/schemas/2004"')

    --insert @view (value) values (' elementFormDefault="unqualified" ')
    --insert @view (value) values (' attributeFormDefault="unqualified" >')
    --insert @view (value) values ('')
    --insert @view (value) values (' <xsd:import namespace="http://www.w3.org/XML/1998/namespace"')
    --insert @view (value) values (' schemaLocation="http://www.w3.org/2001/xml.xsd" />')
    insert @view (value) values ('')

    insert @view (value) values ('')
    insert @view (value) values (' <xsd:annotation>')
    insert @view (value) values (' <xsd:documentation xml:lang="en">')
    insert @view (value) values (' Copyright (c) ' + cast(year(getutcdate()) as nvarchar) + ' Microsoft Corp. All rights reserved.')
    insert @view (value) values (' DO NOT EDIT - Schema automatically generated ')
    insert @view (value) values (' Built on : ' + cast(@buildDate as nvarchar))
    insert @view (value) values (' Version : ' + cast(@buildNumber as nvarchar))
    insert @view (value) values (' </xsd:documentation>')
    insert @view (value) values (' </xsd:annotation>')
    insert @view (value) values ('')

    insert @view (value) values (' <xsd:simpleType name="uniqueidentifier">')
    insert @view (value) values (' <xsd:restriction base="xsd:string">')
    insert @view (value) values (' <xsd:pattern value="[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" /> ')
    insert @view (value) values (' </xsd:restriction>')
    insert @view (value) values (' </xsd:simpleType>')
    insert @view (value) values ('')

    insert @view (value) values (' <xsd:complexType name="keyType">')
    insert @view (value) values (' <xsd:simpleContent>')
    insert @view (value) values (' <xsd:extension base="xsd:string">')
    insert @view (value) values (' </xsd:extension>')
    insert @view (value) values (' </xsd:simpleContent>')
    insert @view (value) values (' </xsd:complexType>')
    insert @view (value) values ('')

    insert @view (value) values (' <xsd:complexType name="principalType">')
    insert @view (value) values (' <xsd:simpleContent>')
    insert @view (value) values (' <xsd:extension base="xsd:string">')
    insert @view (value) values (' <xsd:attribute name="name" type="xsd:string" />')
    insert @view (value) values (' <!-- modified from xsd:int -->')
    insert @view (value) values (' <xsd:attribute name="type" type="xsd:string" use="required" />')
    insert @view (value) values (' <!-- modified from xsd:int -->')
    insert @view (value) values (' <xsd:attribute name="dsc" type="xsd:string" />')
    insert @view (value) values (' </xsd:extension>')
    insert @view (value) values (' </xsd:simpleContent>')
    insert @view (value) values (' </xsd:complexType>')
    insert @view (value) values ('')

    insert @view (value) values (' <xsd:complexType name="ownerType">')
    insert @view (value) values (' <xsd:simpleContent>')
    insert @view (value) values (' <xsd:extension base="xsd:string">')
    insert @view (value) values (' <xsd:attribute name="name" type="xsd:string" />')
    insert @view (value) values (' <!-- modified from xsd:int -->')
    insert @view (value) values (' <xsd:attribute name="type" type="xsd:string" use="required" />')
    insert @view (value) values (' <!-- modified from xsd:int -->')
    insert @view (value) values (' <xsd:attribute name="dsc" type="xsd:string" />')
    insert @view (value) values (' </xsd:extension>')
    insert @view (value) values (' </xsd:simpleContent>')
    insert @view (value) values (' </xsd:complexType>')
    insert @view (value) values ('')

    insert @view (value) values (' <xsd:complexType name="customerType">')
    insert @view (value) values (' <xsd:simpleContent>')
    insert @view (value) values (' <xsd:extension base="xsd:string">')
    insert @view (value) values (' <xsd:attribute name="name" type="xsd:string" />')
    insert @view (value) values (' <!-- modified from xsd:int -->')
    insert @view (value) values (' <xsd:attribute name="type" type="xsd:string" use="required" />')
    insert @view (value) values (' <!-- modified from xsd:int -->')
    insert @view (value) values (' <xsd:attribute name="dsc" type="xsd:string" />')
    insert @view (value) values (' </xsd:extension>')
    insert @view (value) values (' </xsd:simpleContent>')
    insert @view (value) values (' </xsd:complexType>')
    insert @view (value) values ('')

    insert @view (value) values (' <xsd:complexType name="lookupType">')
    insert @view (value) values (' <xsd:simpleContent>')
    insert @view (value) values (' <xsd:extension base="xsd:string">')
    insert @view (value) values (' <xsd:attribute name="name" type="xsd:string" />')
    insert @view (value) values (' <!-- modified from xsd:int -->')
    insert @view (value) values (' <xsd:attribute name="type" type="xsd:string" use="required" />')
    insert @view (value) values (' <!-- modified from xsd:int -->')
    insert @view (value) values (' <xsd:attribute name="dsc" type="xsd:string" />')
    insert @view (value) values (' </xsd:extension>')
    insert @view (value) values (' </xsd:simpleContent>')
    insert @view (value) values (' </xsd:complexType>')
    insert @view (value) values ('')

    insert @view (value) values (' <xsd:complexType name="picklistType">')
    insert @view (value) values (' <xsd:simpleContent>')
    insert @view (value) values (' <!-- modified from xsd:int -->')
    insert @view (value) values (' <xsd:extension base="xsd:string">')
    insert @view (value) values (' <xsd:attribute name="name" type="xsd:string"/>')
    insert @view (value) values (' </xsd:extension>')
    insert @view (value) values (' </xsd:simpleContent>')
    insert @view (value) values (' </xsd:complexType>')
    insert @view (value) values ('')

    insert @view (value) values (' <xsd:complexType name="booleanType">')
    insert @view (value) values (' <xsd:simpleContent>')
    insert @view (value) values (' <!-- modified from xsd:boolean -->')
    insert @view (value) values (' <xsd:extension base="xsd:string">')
    insert @view (value) values (' <xsd:attribute name="name" type="xsd:string"/>')
    insert @view (value) values (' </xsd:extension>')
    insert @view (value) values (' </xsd:simpleContent>')
    insert @view (value) values (' </xsd:complexType>')
    insert @view (value) values ('')

    insert @view (value) values (' <xsd:complexType name="moneyType">')
    insert @view (value) values (' <xsd:simpleContent>')
    insert @view (value) values (' <!-- modified from xsd:float -->')
    insert @view (value) values (' <xsd:extension base="xsd:string">')
    insert @view (value) values (' <xsd:attribute name="value" type="xsd:string"/>')
    insert @view (value) values (' </xsd:extension>')
    insert @view (value) values (' </xsd:simpleContent>')
    insert @view (value) values (' </xsd:complexType>')
    insert @view (value) values ('')

    insert @view (value) values (' <xsd:complexType name="numberType">')
    insert @view (value) values (' <xsd:simpleContent>')
    insert @view (value) values (' <!-- modified from xsd:int -->')
    insert @view (value) values (' <xsd:extension base="xsd:string">')
    insert @view (value) values (' <xsd:attribute name="value" type="xsd:string"/>')
    insert @view (value) values (' </xsd:extension>')
    insert @view (value) values (' </xsd:simpleContent>')
    insert @view (value) values (' </xsd:complexType>')
    insert @view (value) values ('')

    insert @view (value) values (' <xsd:complexType name="decimalType">')
    insert @view (value) values (' <xsd:simpleContent>')
    insert @view (value) values (' <!-- modified from xsd:float -->')
    insert @view (value) values (' <xsd:extension base="xsd:string">')
    insert @view (value) values (' <xsd:attribute name="value" type="xsd:string"/>')
    insert @view (value) values (' </xsd:extension>')
    insert @view (value) values (' </xsd:simpleContent>')
    insert @view (value) values (' </xsd:complexType>')
    insert @view (value) values ('')

    insert @view (value) values (' <xsd:complexType name="floatType">')
    insert @view (value) values (' <xsd:simpleContent>')
    insert @view (value) values (' <!-- modified from xsd:float -->')
    insert @view (value) values (' <xsd:extension base="xsd:string">')
    insert @view (value) values (' <xsd:attribute name="value" type="xsd:string"/>')
    insert @view (value) values (' </xsd:extension>')
    insert @view (value) values (' </xsd:simpleContent>')
    insert @view (value) values (' </xsd:complexType>')
    insert @view (value) values ('')

    insert @view (value) values (' <xsd:complexType name="dateTimeType">')
    insert @view (value) values (' <xsd:simpleContent>')
    insert @view (value) values (' <!-- modified from xsd:dateTime -->')
    insert @view (value) values (' <xsd:extension base="xsd:string">')
    insert @view (value) values (' <xsd:attribute name="date" type="xsd:string"/>')
    insert @view (value) values (' <xsd:attribute name="time" type="xsd:string"/>')
    insert @view (value) values (' </xsd:extension>')
    insert @view (value) values (' </xsd:simpleContent>')
    insert @view (value) values (' </xsd:complexType>')
    insert @view (value) values ('')

    insert @view (value) values (' <xsd:complexType name="statusType">')
    insert @view (value) values (' <xsd:simpleContent>')
    insert @view (value) values (' <!-- modified from xsd:int -->')
    insert @view (value) values (' <xsd:extension base="xsd:string">')
    insert @view (value) values (' <xsd:attribute name="name" type="xsd:string"/>')
    insert @view (value) values (' </xsd:extension>')
    insert @view (value) values (' </xsd:simpleContent>')
    insert @view (value) values (' </xsd:complexType>')
    insert @view (value) values ('')

    insert @view (value) values (' <xsd:complexType name="stateType">')
    insert @view (value) values (' <xsd:simpleContent>')
    insert @view (value) values (' <!-- modified from xsd:int -->')
    insert @view (value) values (' <xsd:extension base="xsd:string">')
    insert @view (value) values (' <xsd:attribute name="name" type="xsd:string"/>')
    insert @view (value) values (' </xsd:extension>')
    insert @view (value) values (' </xsd:simpleContent>')
    insert @view (value) values (' </xsd:complexType>')
    insert @view (value) values ('')

    -- open the cursor
    open entityCursor
    fetch entityCursor into @entityName

    while @@fetch_status = 0
    begin

    insert @view (value) values (' <xsd:element name="' + @entityName + '">')
    insert @view (value) values (' <xsd:complexType>')
    insert @view (value) values (' <xsd:sequence>')

    declare attributeCursor cursor for
    select Attribute.LogicalName,
    case
    when AttributeTypes.XmlType in ('dateTime.tz', 'datetime') then 'dateTimeType'
    when AttributeTypes.XmlType = 'Boolean' then 'booleanType'

    when AttributeTypes.XmlType = 'picklist' then 'picklistType'
    when AttributeTypes.XmlType = 'state' then 'stateType'
    when AttributeTypes.XmlType = 'status' then 'statusType'

    when AttributeTypes.XmlType = 'primarykey' then 'keyType'
    when AttributeTypes.XmlType = 'customer' then 'customerType'
    when AttributeTypes.XmlType = 'lookup' then 'lookupType'
    when AttributeTypes.XmlType = 'owner' then 'ownerType'

    when AttributeTypes.XmlType = 'uuid' then 'keyType'

    when AttributeTypes.XmlType = 'timezone' then 'xsd:int'
    when AttributeTypes.XmlType in ('integer', 'int', 'bigint', 'smallint', 'tinyint') then 'numberType'
    when AttributeTypes.Description = 'money' then 'moneyType'
    when AttributeTypes.Description = 'decimal' then 'decimalType'
    when AttributeTypes.Description = 'float' then 'floatType'

    else 'xsd:' + AttributeTypes.XmlType
    end
    from Entity join Attribute on (Entity.EntityId = Attribute.EntityId)
    join AttributeTypes on (Attribute.AttributeTypeId = AttributeTypes.AttributeTypeId)
    where Entity.LogicalName = @entityName
    and (Attribute.ValidForReadAPI = 1 or Attribute.ValidForUpdateAPI = 1 or Attribute.ValidForCreateAPI = 1)
    and Attribute.AttributeOf is NULL
    and Attribute.AggregateOf is NULL
    order by Attribute.ColumnNumber

    open attributeCursor
    fetch attributeCursor into @attributeName, @typeName

    while @@fetch_status = 0
    begin
    insert @view (value) values (' <xsd:element name="' + @attributeName + '" type="' + @typeName + '" />')
    fetch attributeCursor into @attributeName, @typeName
    end

    close attributeCursor
    deallocate attributeCursor

    insert @view (value) values (' </xsd:sequence>')
    insert @view (value) values (' </xsd:complexType>')
    insert @view (value) values (' </xsd:element>')

    fetch entityCursor into @entityName

    if @@fetch_status = 0
    begin
    insert @view (value) values ('')
    end
    end

    close entityCursor
    deallocate entityCursor

    insert @view (value) values ('</xsd:schema>')

    select value
    from @view order by idvalue
    </font>