Dailycode.info

Short solution for short problems

.Net Selftracking entities loose context after serialization.

You can encounter lots of weird problems when you are working with self tracking entities that loose their context. Certainly when you try to save. Insert, delete and selects will not really depend on their context. But saving can give you problems:

For example, when you send the entity over WCF and change something on a non .Net client. Sometimes he will just save it. But sometimes you will get the error that an object with that key is already in the context. Or a unique key constraint or whatever. I tried several things to solve it. Many tries where good, but none was stable and perfect. 

Now I find I've found a trustworthy and stable solution. I got to it when testing al kinds of automapper implementations. The goal was to map the entity's properties that was decoupled from the context with the entity that was in the context. But very soon I noticed that most automapper just create a new instance of the object and map the properties to this. So that was no help. What I needed was an automapper that would map the properties of one instance to the properties of another instance (the one form the context) and not a new instance with comined properties. So I found mapper code that did the trick here. I'm using the second implementation of the cached property maps. Not using the static implementation, but rather a Singleton. So I can put the initial creation of all mappings in the constructor. To map the object coming from the webservices to the object from the context is simple:

//Get the object from the context

GalvaSFIMobileEntities.GP_MES_F06ByOrder fc = PL.GetF06OrderInfo(f06.SalesOrder);

//Map the object's properties to the properties of the context object

GenericSingleton<SelfTrackingObjectMapper>.GetInstance().CopyMatchingCachedProperties(f06, fc);

//Save the object from the context with the matched proprties.

PL.SaveF06ByOrder(fc);

Now for the implementation of this mapper. Just add a class to your project and past this code:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Reflection;

using System.Text;

using Microsoft.CSharp;

 

public class PropertyMap

{

 

    public PropertyInfo SourceProperty { get; set; }

 

    public PropertyInfo TargetProperty { get; set; }

 

}

 

 

///<summary>

/// Summary description for SelfTrackingObjectMapper

///</summary>

public class SelfTrackingObjectMapper

{

    public SelfTrackingObjectMapper()

    {

        //

        // TODO: Add constructor logic here

        //

        AddPropertyMap<GalvaSFIMobileEntities.GP_MES_F06ByOrder, GalvaSFIMobileEntities.GP_MES_F06ByOrder>();

        AddPropertyMap<GalvaSFIMobileEntities.GP_MES_F06ByOrderDetails, GalvaSFIMobileEntities.GP_MES_F06ByOrderDetails>();

    }

 

    public IList<PropertyMap> GetMatchingProperties(Type sourceType, Type targetType)

    {

        var sourceProperties = sourceType.GetProperties();

        var targetProperties = targetType.GetProperties();

 

        var properties = (from s in sourceProperties

                          from t in targetProperties

                          where s.Name == t.Name &&

                                s.CanRead &&

                                t.CanWrite &&

                                s.PropertyType.IsPublic &&

                                t.PropertyType.IsPublic &&

                                s.PropertyType == t.PropertyType &&

                                (

                                  (s.PropertyType.IsValueType &&

                                   t.PropertyType.IsValueType

                                  ) ||

                                  (s.PropertyType == typeof(string) &&

                                   t.PropertyType == typeof(string)

                                  )

                                )

                          select new PropertyMap

                                     {

                                         SourceProperty = s,

                                         TargetProperty = t

                                     }).ToList();

        return properties;

    }

 

 

    private Dictionary<string, PropertyMap[]> _maps =

    new Dictionary<string, PropertyMap[]>();

 

 

    public void AddPropertyMap<T, TU>()

    {

        var props = GetMatchingProperties(typeof(T), typeof(TU));

        var className = GetClassName(typeof(T), typeof(TU));

        _maps.Add(className, props.ToArray());

    }

 

 

 

    public void CopyMatchingCachedProperties(object source, object target)

    {

        var className = GetClassName(source.GetType(),target.GetType());

        var propMap = _maps[className];

 

        for (var i = 0; i < propMap.Length; i++)

        {

            var prop = propMap[i];

            var sourceValue = prop.SourceProperty.GetValue(source, null);

            prop.TargetProperty.SetValue(target, sourceValue, null);

        }

    }

 

    public string GetClassName(Type sourceType,Type targetType)

    {

        var className = "Copy_";

        className += sourceType.FullName.Replace(".", "_");

        className += "_";

        className += targetType.FullName.Replace(".", "_");

        return className;

    }

}

I choose to keep creation of the mappings based on the properties of both objects. I could in my case only use the properties of the frist object, since they should be the same, but the mappings are only created once and maybe in the future I could use this for other purposes. Eg. POCO -> Self tracking entities. 

Last thing you will need is the GenericSingleton class, you can find this on google, but for the sake of completeness:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace GP_Global

{

    public class GenericSingleton<T> where T : class, new()

    {

        private static T instance;

       

        public static T GetInstance()

        {

            lock (typeof(T))

            {

                if (instance == null)

                {

                    instance = new T();

                }

                return instance;

            }

        }

    }

}