Deep Clone with Expression.New and Expression Trees

c# deep-copy expression-trees

Question

I have two Generated Interfaces IPerson and IAddress.

However I have defined Property Interfaces which Inherit from those base Interfaces

Interfaces

public interface IPerson_Name : IPerson { String Name{get;set;}}

public interface IPerson_Addresses : IPerson
{
    ICollection<IAddress> Addresses{ get; set; }
    IAddress NewAddress();
}
public interface IAddress_Line1 : IAddress
{
    String Line1 { get; set; }
}

Then I have two implementations of each of the base interfaces

Implementation

public class Person : IPerson
{
    public String Name { get; set; }
    public ICollection<Address> Addresses { get; set; }
}
public class PersonPoco : IPerson_Name, IPerson_Addresses
{
    public string Name { get; set; }
    public ICollection<IAddress> Addresses { get; set; }
    public IAddress NewAddress()
    {
        return new AddressPoco();
    }
}
public class Address : IAddress
{
    public String Line1 { get; set; }
}
public class AddressPoco : IAddress_Line1
{
    public String Line1 { get; set; }
}

I'm trying to build expression trees to Convert between PersonPoco and Person as well as any other IPerson

I'm creating a CopyTo function as such.

Copy Extenstion

public static class IPersonExt
{
    public static IQueryable<TDest> CopyTo<TSrc,TDest>(this IQueryable<TSrc> persons) 
        where TSrc : IPerson, new()
        where TDest: IPerson, new()
    {

        var innerLambda = (LambdaExpression)CopyTo(typeof (TSrc), typeof (TDest));
        var copyExpr = Expression.Lambda<Func<TSrc, TDest>>(innerLambda.Body, innerLambda.Parameters);
        return persons.Select(copyExpr);
    }
    internal static LambdaExpression CopyTo(Type tSrc, Type tDest)
    {
        var dest = Activator.CreateInstance(tDest);
        var personparam = Expression.Parameter(tSrc);

        var destNewExpr = Expression.New(tDest);
        var memberbindings = new List<MemberBinding>();

        IPerson_Name destName;
        IPerson_Name srcName;
        if ((tDest.IsInstanceOfType(typeof(IPerson_Name)) || tDest.IsInstanceOfType(typeof(Person))) &&
            (tSrc.IsInstanceOfType(typeof(IPerson_Name)) || tSrc.IsInstanceOfType(typeof(Person))))
        {
            memberbindings.Add(Expression.Bind(tDest.GetProperty("Name"), Expression.Property(personparam, "Name")));
        }

        var toEntityParameterExpression = Expression.MemberInit(destNewExpr, memberbindings);

        return Expression.Lambda(
                toEntityParameterExpression,
                personparam
            );
    }
}

My intention is to save the compiled output for every implementation to implementation conversion of IPerson and IAddress.

If you want more info about my intentions see this question

Edit

I got the base parameter copying to a new version. It's working so far for basic properties. I've updated the code above

Popular Answer

EDIT: Apparently linq to entities can't understand the method calls in these expressions. so this is no help.

Here is a gist

Interface that Implements the following interface will be able to copy properties of their common interfaces.

public interface IEntity
{
}

public interface IEntityObject : IEntity
{
}

public interface IEntityCollection : IEntity
{
}

public interface IEntityProperty : IEntity
{
}

Then for each property of an entity, you need to create an interface

public interface IThing:IEntity{}
public interface IThing_Property: IThing, IEntityProperty
{
    int SimpleProperty{get;set;}
}
public interface IThing_ComplexProperty: IThing, IEntityObject{}
public interface IThing_ComplexProperty<T> : IEntity_ComplexProperty where T:class, IEntity,new()
{
   T SomeProperty{get;set;}
}
public interface IThing_CollectionProperty: IThing, IEntityCollection{}
public interface IThing_CollectionProperty<T> :IEntity_CollectionProperty where T:class, IEntity,new()
{
   IEnumerable<T> SomeCollectionProperty{get;set;}
}

Now if you do

IQueryable<SomeThingImpl> x;
IQueryable<SomeOtherThingImpl> y = x.CopyTo<SomeThingImpl,SomeOtherThingImpl>()

I will not have been called yet until it enumerates.

Also if The generics types share any IEntity Interfaces, they too will be copied over.

For a fuller implementation here's another gist

Here's what the classes look like

public static class CopyToExt
{
    private static readonly ConcurrentDictionary<Type, LambdaExpression> expressions;
    private static readonly ConcurrentDictionary<Type, Object> funcs;
    private static readonly ConcurrentDictionary<Type, LambdaExpression> mergeStackExpr;
    private static ConcurrentDictionary<Type, Object> mergeStackFunc;
    public static MethodInfo AsQueryableMethod;
    public static MethodInfo SelectMethod;
    public static MethodInfo ToListMethod;
    public static MethodInfo CopyToMethod;
    public static MethodInfo AddMergeMethod;
    public static MethodInfo CreateMergeMethod;
    public static MethodInfo TryCopyMethod;


    static CopyToExt()
    {

        mergeStackExpr = new ConcurrentDictionary<Type, LambdaExpression>(new Dictionary<Type, LambdaExpression>());
        foreach (MethodInfo m in typeof(Queryable).GetMethods().Where(m => m.Name == "Select"))
            foreach (ParameterInfo p in m.GetParameters().Where(p => p.Name.Equals("selector")))
                if (p.ParameterType.GetGenericArguments().Any(x => x.GetGenericArguments().Count() == 2))
                    SelectMethod = (MethodInfo)p.Member;
        foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name == "ToList"))
            foreach (ParameterInfo p in m.GetParameters().Where(p => p.Name.Equals("source")))
                if (p.ParameterType.GetGenericArguments().Count() == 1)
                    ToListMethod = (MethodInfo)p.Member;
        foreach (MethodInfo m in typeof(Queryable).GetMethods().Where(m => m.Name == "AsQueryable"))
            foreach (ParameterInfo p in m.GetParameters().Where(p => p.Name.Equals("source")))
                if (p.ParameterType.GetGenericArguments().Count() == 1)
                    AsQueryableMethod = (MethodInfo)p.Member;

        CreateMergeMethod = typeof(CopyToExt).GetMethods().First(m => m.Name == "CreateMergeStack");
        AddMergeMethod = typeof(MergeStack).GetMethods().First(m => m.Name == "AddReturnOrCreateAddReturn");
        TryCopyMethod = typeof(MergeStack).GetMethods().First(m => m.Name == "TryCopy");
    }

    public static IQueryable<TDest> CopyTo<TSrc, TDest>(this IQueryable<TSrc> queryable)
        where TSrc : class, IEntity
        where TDest : class, IEntity
    {
        var copyExpr = CreateTryCopy<TSrc, TDest>();
        var ms = CreateMergeStack<TSrc>();
        return queryable.Select(ms).Select(copyExpr);
    }

    public static IEnumerable<TDest> CopyTo<TSrc, TDest>(this IEnumerable<TSrc> queryable)
        where TSrc : class, IEntity
        where TDest : class, IEntity
    {
        return queryable.AsQueryable().CopyTo<TSrc, TDest>().AsEnumerable();
    }


    public static Expression<Func<TSrc, Stackholder<TSrc>>> CreateMergeStack<TSrc>()
    {
        return mergeStackExpr.GetOrAdd(typeof(TSrc), (type) =>
        {
            var shtype = typeof(Stackholder<TSrc>);
            var parm = Expression.Parameter(typeof(TSrc));
            var destNewExpr = Expression.New(shtype);
            var methodcall = Expression.Call(null,
                CopyToExt.AddMergeMethod);

            var memInit = Expression.MemberInit(destNewExpr,
                Expression.Bind(shtype.GetField("Src"), parm),
                Expression.Bind(shtype.GetField("Ms"), methodcall));
            return Expression.Lambda<Func<TSrc, Stackholder<TSrc>>>(memInit, parm);
        }) as Expression<Func<TSrc, Stackholder<TSrc>>>;
    }

    public static Expression<Func<Stackholder<TSrc>, TDest>> CreateTryCopy<TSrc, TDest>()
    {

        var shtype = typeof(Stackholder<TSrc>);
        var parentParam = Expression.Parameter(shtype);
        var SrcExpr = Expression.PropertyOrField(parentParam, "Src");
        var MSExpr = Expression.PropertyOrField(parentParam, "Ms");
        var tcMethod = Expression.Call(MSExpr,
            TryCopyMethod.MakeGenericMethod(new Type[] { typeof(TSrc), typeof(TDest) }),
            SrcExpr);
        return Expression.Lambda<Func<Stackholder<TSrc>, TDest>>(tcMethod, parentParam);
    }

    internal static bool Impliments(this Type type, Type inheritedType)
    {
        return (type.IsSubclassOf(inheritedType) || type.GetInterface(inheritedType.FullName) != null);
    }

    internal static bool Impliments<T>(this Type type, Type inheritedType = null)
    {
        return type.Impliments(typeof(T));
    }

    private static ConcurrentDictionary<Type, Object> AssignDict =
        new ConcurrentDictionary<Type, Object>(new Dictionary<Type, Object>());
    internal static Func<TSrc, TDest, MergeStack, TDest> Assign<TSrc, TDest>()
    {
        return (Func<TSrc, TDest, MergeStack, TDest>) AssignDict.GetOrAdd(typeof (Func<TSrc, TDest>), (indexType) =>
        {
            var tSrc = typeof (TSrc);
            var tDest = typeof (TDest);
            var srcEntityInterfaces = tSrc.GetInterfaces().Where(x => x.Impliments<IEntity>());
            var destEntityInterfaces = tDest.GetInterfaces().Where(x => x.Impliments<IEntity>());
            var srcParam = Expression.Parameter(tSrc);
            var destParam = Expression.Parameter(tDest);
            var MSExpr = Expression.Parameter(typeof (MergeStack));


            var common = destEntityInterfaces.Intersect(srcEntityInterfaces);

            var memberbindings = common.Where(x => x.Impliments<IEntityProperty>())
                .Select(type => type.GetProperties().First())
                .Select(
                    prop =>
                        Expression.Assign(Expression.Property(destParam, prop.Name),
                            Expression.Property(srcParam, prop.Name)))
                .Cast<Expression>().ToList();


            foreach (var type in common.Where(x => x.Impliments<IEntityObject>()))
            {
                var destSubType = destEntityInterfaces.First(x => x.Impliments(type))
                    .GetGenericArguments()
                    .First();
                var srcSubType = srcEntityInterfaces.First(x => x.Impliments(type))
                    .GetGenericArguments()
                    .First();
                var dProp = destEntityInterfaces.First(x => x.Impliments(type)).GetProperties().First();
                var sProp = srcEntityInterfaces.First(x => x.Impliments(type)).GetProperties().First();

                var tcParam = Expression.Parameter(srcSubType);
                var tcMethod = Expression.Call(MSExpr,
                    TryCopyMethod.MakeGenericMethod(new Type[] {srcSubType, destSubType}),
                    tcParam);
                LambdaExpression mergeLambda = Expression.Lambda(tcMethod, tcParam);

                MemberExpression memberExpression = Expression.Property(srcParam, sProp.Name);
                InvocationExpression invocationExpression = Expression.Invoke(mergeLambda,
                    Expression.Property(srcParam, sProp.Name));
                var check = Expression.Condition(
                    Expression.MakeBinary(ExpressionType.NotEqual, memberExpression,
                        Expression.Constant(null, sProp.PropertyType)), invocationExpression,
                    Expression.Constant(null, invocationExpression.Type));
                BinaryExpression binaryExpression = Expression.Assign(Expression.Property(destParam, dProp.Name),
                    check);
                memberbindings.Add(binaryExpression);
            }

            foreach (var type in common.Where(x => x.Impliments<IEntityCollection>()))
            {
                var destSubType = destEntityInterfaces.First(x => x.Impliments(type))
                    .GetGenericArguments()
                    .First();
                var srcSubType = srcEntityInterfaces.First(x => x.Impliments(type))
                    .GetGenericArguments()
                    .First();
                var dProp = destEntityInterfaces.First(x => x.Impliments(type)).GetProperties().First();
                var sProp = srcEntityInterfaces.First(x => x.Impliments(type)).GetProperties().First();

                var tcParam = Expression.Parameter(srcSubType);
                var tcMethod = Expression.Call(MSExpr,
                    TryCopyMethod.MakeGenericMethod(new Type[] {srcSubType, destSubType}),
                    tcParam);
                LambdaExpression mergeLambda = Expression.Lambda(tcMethod, tcParam);

                var memberExpression = Expression.Property(srcParam, sProp.Name);

                var selectExpr = Expression.Call(null,
                    AsQueryableMethod.MakeGenericMethod(new Type[] {srcSubType}),
                    new Expression[] {memberExpression});
                selectExpr = Expression.Call(null,
                    CopyToExt.SelectMethod.MakeGenericMethod(new Type[] {srcSubType, destSubType}),
                    new Expression[] {selectExpr, mergeLambda});
                selectExpr = Expression.Call(null,
                    CopyToExt.ToListMethod.MakeGenericMethod(new Type[] {destSubType}),
                    new Expression[] {selectExpr});


                var check = Expression.Condition(
                    Expression.MakeBinary(ExpressionType.NotEqual, memberExpression,
                        Expression.Constant(null, sProp.PropertyType)), selectExpr,
                    Expression.Constant(null, selectExpr.Type));

                memberbindings.Add(Expression.Assign(Expression.Property(destParam, dProp.Name), check));
            }
            memberbindings.Add(destParam);
            return
                Expression.Lambda<Func<TSrc, TDest, MergeStack, TDest>>(Expression.Block(memberbindings),
                    new ParameterExpression[] {srcParam, destParam, MSExpr}).Compile();
        });
    }
}

public class Stackholder<TSrc>
{
    public MergeStack Ms;
    public TSrc Src;
}
public class MergeStack
{
    private static ConcurrentDictionary<Thread, MergeStack> StackDict = new ConcurrentDictionary<Thread, MergeStack>(new Dictionary<Thread, MergeStack>());
    private readonly Dictionary<Type, Dictionary<Object, Object>> _mergeObjDict = new Dictionary<Type, Dictionary<object, object>>();

    public static MergeStack AddReturnOrCreateAddReturn()
    {
        return StackDict.GetOrAdd(Thread.CurrentThread, (x) => new MergeStack() { });
    }

    public TDest TryCopy<TSrc, TDest>(TSrc Src)
        where TSrc : class, IEntity
        where TDest : class, IEntity, new()
    {
        if (Src == null) return null;
        var objToIndex = new TDest();
        Dictionary<object, object> objToObj;
        if (!_mergeObjDict.ContainsKey(objToIndex.GetType()))
        {
            objToObj = new Dictionary<object, object>();
            _mergeObjDict.Add(objToIndex.GetType(), objToObj);
        }
        else
        {
            objToObj = _mergeObjDict[objToIndex.GetType()];
        }
        if (!objToObj.ContainsKey(Src))
        {
            objToObj.Add(Src, objToIndex);
            return CopyToExt.Assign<TSrc, TDest>()(Src, objToIndex, this);
        }
        return objToObj[Src] as TDest;

    }

}

Here is a text template file to generate said classes from an EF diagram



Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why