(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>