#Crm2Crm – part 5: Serialization fixed

I ended my previous article in this replication series with the remark that I needed to focus on the serialization and deserialization of the entity data that I’m going to replicate. I explained that I have to do the serialization by hand because the CRM sandbox is preventing me from using binary serialization (as (Microsoft considers a number of functions in the Microsoft .Net libraries to be unsafe / untrusted).

In the initial version of the serialization, I created an XML representation of the source entity as you can see in the code snippet below.

Program1

Running this program gives the ouput below:

Output1

Serializing the entity data like this, would give us headaches when we want to deserialize the data back to an entity. As the decimal and money fields are using the systems locale settings in the XML representation. Furthermore looking at the XML, no one can tell what the original field type used to be.

In other words, when serializing the data, I need to add information regarding the source type of the data, and I need to ensure that all numeric values are serialized in a numeric format that the system understands (en-us). In order to do that I decided to write the output fields in the following format

<FIELDNAME> System Type | [Field value][Extended CRM Type] </ENTITY>

This results in an XML representation as shown in the figure below:

Output2

When I use this XML object to deserialize the data back to an entity, the visual studio output shows exact an exact copy of the entity which was put in.

Serialize1

In the serialization and deserialization routines, I ensure that I set the Id of the original entity. This way we can guarantee that the Id of the deserialized entity matches the source, paving the way for doing Upsert actions in the replication process.

In the source snippet below I published the source code of the serialization/deserialization routines.

// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) 2016 Bas van de Sande - JourneyIntoCRM - http://journeyintocrm.com 
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
using System;
using System.Text;
using Microsoft.Xrm.Sdk;
using System.Data;
using System.Globalization;
using System.IO;
 
namespace SerializationTest
{
    public class EntitySerializer
    {
        public string SerializeObject(Entity entity)
        {
            var ds = new DataSet("XmlData");
            var dt = new DataTable(entity.LogicalName);
            ConvertEntityToDataTable(dt, entity);
            ds.Tables.Add(dt);
            return ds.GetXml();
        }
 
        private void ConvertEntityToDataTable(DataTable dataTable, Entity entity)
        {
            DataRow row = dataTable.NewRow();
            dataTable.Columns.Add("Id");
            row["Id"] = getAttributeValue(entity.Id).ToString();
            foreach (var attribute in entity.Attributes)
            {
                if (!dataTable.Columns.Contains(attribute.Key))
                    dataTable.Columns.Add(attribute.Key);
                row[attribute.Key] = getAttributeValue(attribute.Value).ToString();
            }
            foreach (var fv in entity.FormattedValues)
            {
                if (!dataTable.Columns.Contains(fv.Key + "name"))
                    dataTable.Columns.Add(fv.Key + "name");
                row[fv.Key + "name"] = fv.Value;
            }
            dataTable.Rows.Add(row);
        }
 
        private string getAttributeValue(object entityValue)
        {
            var objectValue = "";
            var objectType = "";
            var objectReference = "";
 
            objectType = $"{entityValue.GetType()}";
 
            switch (entityValue.ToString())
            {
                case "Microsoft.Xrm.Sdk.EntityReference":
                    objectValue = ((EntityReference)entityValue).Id.ToString();
                    objectReference = ((EntityReference)entityValue).LogicalName;
                    break;
                case "Microsoft.Xrm.Sdk.OptionSetValue":
                    objectValue = ((OptionSetValue)entityValue).Value.ToString();
                    break;
                case "Microsoft.Xrm.Sdk.Money":
                    objectValue = Convert.ToString(((Money)entityValue).Value, CultureInfo.InvariantCulture.NumberFormat);
                    break;
                case "Microsoft.Xrm.Sdk.AliasedValue":
                    var av = (Microsoft.Xrm.Sdk.AliasedValue)entityValue;
                    objectValue = getAttributeValue(av.Value);
                    objectReference= $"{av.EntityLogicalName},{av.AttributeLogicalName}"; 
                    break;
                case "System.Guid":
                    objectValue = entityValue.ToString();
                    break;
                default:
                    if (entityValue.IsNumeric())
                        objectValue = Convert.ToString(entityValue, CultureInfo.InvariantCulture.NumberFormat);
                    else if (entityValue.IsDateTime())
                        objectValue = ((DateTime)entityValue).ToUniversalTime().ToString("u");
                    else
                        objectValue = entityValue.ToString();
                    break;
            }
 
            if (!string.IsNullOrWhiteSpace(objectReference)) objectReference += ",";
 
            return $"{objectType}|{objectReference}{objectValue}";
        }
 
 
        public static Entity DeserializeObject(string xml)
        {
            Entity entity = null;
            var dataSet = new DataSet();
 
            byte[] xmlBytes = Encoding.UTF8.GetBytes(xml);
 
            using (MemoryStream ms = new MemoryStream(xmlBytes))
            {
                dataSet.ReadXml(ms);
            }
            if (dataSet.Tables.Count > 0)
            {
                var dt = dataSet.Tables[0];
 
                if (dt.Rows.Count > 0)
                {
                    entity = new Entity(dt.TableName);
                    var row = dt.Rows[0];
 
                    foreach (DataColumn column in dt.Columns)
                    {
                        if (column.ColumnName == "Id")
                            entity.Id = (Guid)SetAttributeValue(row[column.ColumnName]);
                        else
                            entity[column.ColumnName] = SetAttributeValue(row[column.ColumnName]);
                    }
                }
            }
 
            return entity;
        }
 
        private static object SetAttributeValue(object val)
        {
            if (val != null)
            {
                var input = val.ToString();
                var tokens = input.Split('|');
 
                if (tokens.Length == 2)
                {
 
                    switch (tokens[0])
                    {
                        case "Microsoft.Xrm.Sdk.EntityReference":
                            var er = tokens[1].Split(',');
                            if (er.Length == 2) return new EntityReference(er[0], new Guid(er[1]));
                            break;
                        case "Microsoft.Xrm.Sdk.OptionSetValue":
                            var i = int.MinValue;
                            int.TryParse(tokens[1], out i);
                            if (i != int.MinValue) return new OptionSetValue(i);
                            break;
                        case "Microsoft.Xrm.Sdk.Money":
                            var d = Decimal.MinValue;
                            decimal.TryParse(tokens[1], NumberStyles.AllowDecimalPoint,NumberFormatInfo.InvariantInfo, out d);
                            if (d != decimal.MinValue) return new Money(d);
                            break;
                        case "Microsoft.Xrm.Sdk.AliasedValue":
                            var av = tokens[1].Split(',');
                            if (av.Length==3) return new AliasedValue(av[0],av[1], SetAttributeValue(av[2]));
                            break;
                        case "System.Guid":
                            var g = Guid.Empty;
                            Guid.TryParse(tokens[1], out g);
                            if (g != Guid.Empty) return g;
                            break;
                        case "System.DateTime":
                            var dt = DateTime.MinValue;
                            DateTime.TryParse(tokens[1],out dt);
                            if (dt != DateTime.MinValue) return dt.ToUniversalTime();
                            break;
                        case "System.Int32":
                            var i32 = int.MinValue;
                            int.TryParse(tokens[1], out i32);
                            if (i32 != int.MinValue) return i32;
                            break;
                        case "System.Boolean":
                            return (tokens[1].ToUpper() == "TRUE");
                        case "System.Decimal":
                            var sd = Decimal.MinValue;
                            decimal.TryParse(tokens[1], NumberStyles.AllowDecimalPoint, NumberFormatInfo.InvariantInfo, out sd);
                            if (sd != decimal.MinValue) return sd;
                            break;
                        default:
                            // all other values
                            return tokens[1].ToString();
                    }
                }
            }
            return null;
        }
    }
 
    public static class ExtensionMethods
    {
        public static bool IsNumeric(this object obj)
        {
            switch (Type.GetTypeCode(obj.GetType()))
            {
                case TypeCode.Byte:
                case TypeCode.SByte:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.Decimal:
                case TypeCode.Double:
                case TypeCode.Single:
                    return true;
                default:
                    return false;
            }
        }
        public static bool IsDateTime(this object obj)
        {
            return (Type.GetTypeCode(obj.GetType()) == TypeCode.DateTime);
        }
 
    }
 
}

In the next article, I’ll continue to work on the replication and setup a one way replication between two entities. From that point I can easily extend it to a bidirectional version.

But first things first… I need to think on how to implement the message pump.

As always, stay tuned!

5 thoughts on “#Crm2Crm – part 5: Serialization fixed

  1. Abood Hamwi says:

    Hi
    this is a greet work
    thanks for it
    i just want to check something is it a bug from my side or yours
    you have this lines in ConvertEntityToDataTable method
    foreach (var fv in entity.FormattedValues)
    {
    if (!dataTable.Columns.Contains(fv.Key + “name”))
    dataTable.Columns.Add(fv.Key + “name”);
    row[fv.Key + “name”] = fv.Value;
    }
    in DeserializeObject method you are setting them as attribute in entity so it’s throwing error for me
    is this usual or no

    • Hi Abood,

      can you send me your piece of source code, then I can take a look at it

      Bas

      • Abood Hamwi says:

        public class EntitySerializer
        {
        public string SerializeObject(Entity entity)
        {
        var ds = new DataSet(“XmlData”);
        var dt = new DataTable(entity.LogicalName);
        ConvertEntityToDataTable(dt, entity);
        ds.Tables.Add(dt);
        return ds.GetXml();
        }

        private void ConvertEntityToDataTable(DataTable dataTable, Entity entity)
        {
        DataRow row = dataTable.NewRow();
        dataTable.Columns.Add(“Id”);
        row[“Id”] = getAttributeValue(entity.Id).ToString();
        foreach (var attribute in entity.Attributes)
        {
        if (!dataTable.Columns.Contains(attribute.Key))
        dataTable.Columns.Add(attribute.Key);
        row[attribute.Key] = getAttributeValue(attribute.Value).ToString();
        }
        dataTable.Rows.Add(row);
        }

        private string getAttributeValue(object entityValue)
        {
        var objectValue = “”;
        var objectType = “”;
        var objectReference = “”;
        if (entityValue == null)
        {
        return “null”;
        }
        objectType = $”{entityValue.GetType()}”;

        switch (entityValue.ToString())
        {
        case “Microsoft.Xrm.Sdk.EntityReference”:
        objectValue = ((EntityReference)entityValue).Id.ToString();
        objectReference = ((EntityReference)entityValue).LogicalName;
        break;
        case “Microsoft.Xrm.Sdk.OptionSetValue”:
        objectValue = ((OptionSetValue)entityValue).Value.ToString();
        break;
        case “Microsoft.Xrm.Sdk.Money”:
        objectValue = Convert.ToString(((Money)entityValue).Value, CultureInfo.InvariantCulture.NumberFormat);
        break;
        case “Microsoft.Xrm.Sdk.AliasedValue”:
        var av = (Microsoft.Xrm.Sdk.AliasedValue)entityValue;
        objectValue = getAttributeValue(av.Value);
        objectReference = $”{av.EntityLogicalName},{av.AttributeLogicalName}”;
        break;
        case “System.Guid”:
        objectValue = entityValue.ToString();
        break;
        default:
        if (entityValue.IsNumeric())
        objectValue = Convert.ToString(entityValue, CultureInfo.InvariantCulture.NumberFormat);
        else if (entityValue.IsDateTime())
        objectValue = ((DateTime)entityValue).ToUniversalTime().ToString(“u”);
        else
        objectValue = entityValue.ToString();
        break;
        }

        if (!string.IsNullOrWhiteSpace(objectReference)) objectReference += “,”;

        return $”{objectType}|{objectReference}{objectValue}”;
        }

        public static Entity DeserializeObject(string xml)
        {
        Entity entity = null;
        var dataSet = new DataSet();

        byte[] xmlBytes = Encoding.UTF8.GetBytes(xml);

        using (MemoryStream ms = new MemoryStream(xmlBytes))
        {
        dataSet.ReadXml(ms);
        }
        if (dataSet.Tables.Count > 0)
        {
        var dt = dataSet.Tables[0];

        if (dt.Rows.Count > 0)
        {
        entity = new Entity(dt.TableName);
        var row = dt.Rows[0];

        foreach (DataColumn column in dt.Columns)
        {
        if (column.ColumnName == “Id”)
        entity.Id = (Guid)SetAttributeValue(row[column.ColumnName]);
        else
        entity[column.ColumnName] = SetAttributeValue(row[column.ColumnName]);
        }
        }
        }

        return entity;
        }

        private static object SetAttributeValue(object val)
        {
        if (val != null)
        {
        var input = val.ToString();
        var tokens = input.Split(‘|’);

        if (tokens.Length == 2)
        {

        switch (tokens[0])
        {
        case “Microsoft.Xrm.Sdk.EntityReference”:
        var er = tokens[1].Split(‘,’);
        if (er.Length == 2) return new EntityReference(er[0], new Guid(er[1]));
        break;
        case “Microsoft.Xrm.Sdk.OptionSetValue”:
        var i = int.MinValue;
        int.TryParse(tokens[1], out i);
        if (i != int.MinValue) return new OptionSetValue(i);
        break;
        case “Microsoft.Xrm.Sdk.Money”:
        var d = Decimal.MinValue;
        decimal.TryParse(tokens[1], NumberStyles.AllowDecimalPoint, NumberFormatInfo.InvariantInfo, out d);
        if (d != decimal.MinValue) return new Money(d);
        break;
        case “Microsoft.Xrm.Sdk.AliasedValue”:
        var av = tokens[1].Split(‘,’);
        if (av.Length == 3) return new AliasedValue(av[0], av[1], SetAttributeValue(av[2]));
        break;
        case “System.Guid”:
        var g = Guid.Empty;
        if (Guid.TryParse(tokens[1], out g))
        { return g; }
        break;
        case “System.DateTime”:
        var dt = DateTime.MinValue;
        DateTime.TryParse(tokens[1], out dt);
        if (dt != DateTime.MinValue) return dt.ToUniversalTime();
        break;
        case “System.Int32”:
        var i32 = int.MinValue;
        int.TryParse(tokens[1], out i32);
        if (i32 != int.MinValue) return i32;
        break;
        case “System.Boolean”:
        return (tokens[1].ToUpper() == “TRUE”);
        case “System.Decimal”:
        var sd = Decimal.MinValue;
        decimal.TryParse(tokens[1], NumberStyles.AllowDecimalPoint, NumberFormatInfo.InvariantInfo, out sd);
        if (sd != decimal.MinValue) return sd;
        break;
        default:
        // all other values
        return tokens[1].ToString();
        }
        }
        else if (tokens.Length > 2)
        {
        return input.Remove(0,input.IndexOf(“|”)+1);
        }
        }
        return null;
        }
        }

  2. Abood Hamwi says:

    I also made adjustment
    in method getAttributeValue
    if i want to set field value as null it will throw an error
    i add this condition
    if (entityValue == null)
    {
    return “null”;
    }

  3. Paul says:

    This really helped me. Thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *