Link to home
Start Free TrialLog in
Avatar of Marc Davis
Marc DavisFlag for United States of America

asked on

Copy nested objects of an object to another of the same type.

I am trying to copy the properties from one object and this code works absolutely great with one exception.  If there is another object with the object (i.e. nested) then it will not copy the nested.

Here is the same which was posted on another location:

    public static class Reflection
    {
        /// <summary>
        /// Extension for 'Object' that copies the properties to a destination object.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="destination">The destination.</param>
        public static void CopyProperties(this object source, object destination)
        {
            // If any this null throw an exception
            if (source == null || destination == null)
                throw new Exception("Source or/and Destination Objects are null");
            // Getting the Types of the objects
            Type typeDest = destination.GetType();
            Type typeSrc = source.GetType();
            // Collect all the valid properties to map
            var results = from srcProp in typeSrc.GetProperties()
                          let targetProperty = typeDest.GetProperty(srcProp.Name)
                          where srcProp.CanRead
                          && targetProperty != null
                          && (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
                          && (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
                          && targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)
                          select new { sourceProperty = srcProp, targetProperty = targetProperty };
            //map the properties
            foreach (var props in results)
            {
                props.targetProperty.SetValue(destination, props.sourceProperty.GetValue(source, null), null);
            }
        }
    }

Open in new window


Does anybody know how to get the nested objects that is in the source with modifying the above LINQ?

What I am talking about is let's say, for example there is a Person object and that person object has like a Employer and there is a nested object of the Employer Location...something like:

Person
   - FirstName
   - LastName
   - Sex
   - Age
   - House Addr
   - City
   - State
   - Zip
   - Employer (this is where the nested starts)
          - Employer House Addr (nested Employer Object)
          - Employer City (nested Employer object)
          - Employer State (nested Employer object)
          - Employer Zip  ((nested Employer object)
   - Car
   - Family

The above code will copy everything BUT when it gets to the Employer nested object it does not get anything and copies it as a null.

How can that above be modified also incorporate the nested object?

Any assistance on that would be absolutely greatly appreciated!
Avatar of Chinmay Patel
Chinmay Patel
Flag of India image

Hi davism,

The technique used here is called Reflection (as the class name suggests) and yes it does use Linq to reduce the complexity of the code, Reflection is still the key in the code you have posted. And what you want to achieve is called deep cloning.

Now, as you said, nested objects are not being processed because if there are nested objects, for each such object, We have to call CopyProperties for each of them which the above code does not address.

Please let me know if you must use Reflection or you are open to other methods as well? While I absolutely love power of reflection, I would rather not use it unless and until it is the only way to get things done. One of the method I used a lot back in the days (and if you search around) was serialization/deserialization - you can read my blog on a generic serializer here: https://crmxpress.net/blog/2008/12/generic-serializer-deserializer/

Serialization's performance is questionable(In my experience, it will be slower than the Reflection, but if performance is critical, please do a quick PoC to determine the actual speed difference), but the resultant code footprint is really small and thus, easily maintainable.

Regards,
Chinmay.
Avatar of Marc Davis

ASKER

Hi Chinmay,

Thanks for the response! I have actually tried the serialization and deserialization with the BinaryFormatter but when I serialized to the memorystream and then tried to deserialize to the new object I got the invalid type casting from the 2 objects.

This reflection with the LINQ was the only one that even copied the properties from one object type to the other object type.

So, that said, Reflection with the LINQ isn't the only option. If the serialize/deserialize can work that will be great too. I don't know if I did anything wrong when I did that.

If you have an example situation with the serialization/deserialization please share. One thing to note though...the objects are the same content but 2 different types.

Thanks!
Hi davism,

:D.. that's the catch... that would not work with Serialization without applying some tricks - And I am not sure I would want to go that route. I think we better stick to Reflection. BTW, any specific reason to use BinaryFormatter?

Regards,
Chinmay.
Only because I did something "similar" copy before and it worked but this time since the type of object difference it didn't work.

Can that Reflection with the LINQ be modified to accommodate for the nested objects? If so, how would that be done with the LINQ?
Yes. we can do that. As I do not have the actual objects, may I request you to check the name of the Property's type? and based on that modify the code (roughly) as shown below
    public static class Reflection
    {
        /// <summary>
        /// Extension for 'Object' that copies the properties to a destination object.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="destination">The destination.</param>
        public static void CopyProperties(this object source, object destination)
        {
            // If any this null throw an exception
            if (source == null || destination == null)
                throw new Exception("Source or/and Destination Objects are null");
            // Getting the Types of the objects
            Type typeDest = destination.GetType();
            Type typeSrc = source.GetType();
            // Collect all the valid properties to map
            var results = from srcProp in typeSrc.GetProperties()
                          let targetProperty = typeDest.GetProperty(srcProp.Name)
                          where srcProp.CanRead
                          && targetProperty != null
                          && (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
                          && (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
                          && targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)
                          select new { sourceProperty = srcProp, targetProperty = targetProperty };
            //map the properties
            foreach (var props in results)
            {
                props.targetProperty.SetValue(destination, props.sourceProperty.GetValue(source, null), null);
if((props.targetProperty.GetType().Name(YOURCUSTOMTYPEFULLNAME)))
{
CopyProperties(source, props.targetProperty);
}
            }
        }
    }

Open in new window


I wrote above code without the IDE and have not tested it,  let me know if you run into any issues.
I will give it a shot. It will be tomorrow though. Hopefully that is ok.
Absolutely :)  EE is a democracy :D
Oh one thing right off the cuff...when you mentioned the object destination (i.e. ().Name(YOURCUSTOMTYPEFULLNAME))) ) it could be actually one of 4 possible types. So, there is like object A and the copy of the properties could go to the type of B, C, D, or E. Something like that so I really cannot have it go to one type.
No. That check is to ensure that we do not send any other primitives for recursion.
Right but it's going to still be like the B, C, D, E where those would be like the .Name(YOURCUSTOMTYPEFULLNAME) wouldn't it?

Couldn't I use like the typeDest.Name from the line where: Type typeDest = destination.GetType();

Or are you talking about the .Name(YOURCUSTOMTYPEFULLNAME) of the nested object? Like the "Employer" in the sample?
I am not sure about that, I think if I put it in action in IDE, I will be more clear about it. Or maybe I am just too hungry to understand your question. Rain check?
Yes, absolutely. I will check and work that tomorrow. I will respond back on what I can. Rain check is good. Thx!
Chinmay,

Actually that doesn't work. It is not even getting the nested object in the foreach (i.e. the results). So like "Employer" doesn't even show is a "Name". When setting a watch on it sees all the other ones but the nested objects. It's probably in the LINQ that it's not even getting it.

Any info you can provide after you have look into the IDE use would be great.

Thanks!
Yes. I intend to create couple of classes and intend to check it out. Don't worry. It is reflection, we will be able to get it.
It looks like you're on a path here (essentially you will need to have a recursive cloning mechanism which it seems you are heading towards).

However, unless you really need to write this yourself have you looked at existing libraries?

I found a NuGet package which will do exactly what you need:
https://www.nuget.org/packages/DeepCloner/

It's open source too, if you want to have a peek at the implementation.
Craig,

How would that work? Because unless I a missing something it still has a type issue.  I am seeing "There is no implicit reference conversion..."

Or the typical message of "Cannot implicitly convert type..."  which is all associated with the make purpose of using the reflection.

So, unless, I am missing something I am not seeing how that will work.

It might be best to see what Chinmay may come up with.
Ah so you want to deep clone across two different types? Which is essentially a deep clone + auto-mapping procedure?

In which case maybe you can use the deep clone library first, then use something like AutoMapper to convert that clone to the target type.
Craig,

Any info on the AutoMapper?

Basically any example of what you are referring to with the deep clone and the AutoMapper?
Here is a very rough example, there are more elegant ways to setup AutoMapper such as using profiles and injecting the mapper using DI.

However, for the purpose of illustrating the technique, it works:

using Force.DeepCloner;

namespace UnitTestProject1.code
{
    public class Example
    {
        public Example()
        {
            AutoMapper.Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<Foo, Bar>()
                    .ReverseMap()
                    .ForAllOtherMembers(opt => opt.Ignore());
            });
            ;
        }

        public void ExampleCloneFooToBar()
        {
            var f = new Foo
            {
                FooString = "bla",
                Hello = "world",
                MyComplexType = new ComplexType
                {
                    Number1 = 1,
                    Number2 = 2
                }
            };

            // the magic
            var f2 = f.DeepClone();
            var bar = AutoMapper.Mapper.Map<Bar>(f2);
        }

        public class Foo
        {
            public string Hello { get; set; }
            public ComplexType MyComplexType { get; set; }
            public string FooString { get; set; }
        }

        public class Bar
        {
            public string Hello { get; set; }
            public ComplexType MyComplexType { get; set; }
            public string BarString { get; set; }
        }

        public class ComplexType
        {
            public int Number1;
            public int Number2;
        }
    }
}

Open in new window

Craig,

Actually AutoMapper isn't going to work and cannot be installed but to the .Net Framework that is being used on this. In my mind it should be changed but the potential impact and testing requirements will keep that from happening soon.

So, back to the reflection and that is it possible with the Reflection class noted above with any modification?
SOLUTION
Avatar of Chinmay Patel
Chinmay Patel
Flag of India image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
But what if "a" doesn't have the initial Employer so it's null?

It would then hit that:

if (source == null || destination == null)
                throw new Exception("Source or/and Destination Objects are null");

Open in new window


That's the situation...when I cam copying the sub-object or nested object it was never copied over initially do it will then be null. I need to insert that copied object into it.

How do I go about that?

I think you might have it pretty close Chinmay but I need to get past this null situation where the source had the data in the nested object but the destination does not and I need the destination to have something OR I create a new nested object on the destination with a "new" but how do I go about that in the reflection?
Beauty of reflection is that.... "almost" everything is possible.

Try this one

namespace CrmXpress.EEConsole
{
    using System;
    using System.Linq;
    using System.Reflection;
    public static class Reflection
    {
        /// <summary>
        /// Extension for 'Object' that copies the properties to a destination object.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="destination">The destination.</param>
        public static void CopyProperties(this object source, object destination)
        {
            // If any this null throw an exception
            if (source == null)
                throw new Exception("Source Object is null");
            // Getting the Types of the objects
            Type typeDest = destination.GetType();
            Type typeSrc = source.GetType();
            // Collect all the valid properties to map
            var results = from srcProp in typeSrc.GetProperties()
                          let targetProperty = typeDest.GetProperty(srcProp.Name)
                          where srcProp.CanRead
                          && targetProperty != null
                          && (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
                          && (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
                          && targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)
                          select new { sourceProperty = srcProp, targetProperty = targetProperty };
            //map the properties
            foreach (var props in results)
            {
                props.targetProperty.SetValue(destination, props.sourceProperty.GetValue(source, null), BindingFlags.CreateInstance, null, null, null);
            }
        }
    }

    class A
    {
        public int Id { get; set; }
        public Employer Employer { get; set; }
    }

    class Employer
    {
        public string Name { get; set; }
        public EmployerHouse House { get; set; }
    }
    class EmployerHouse
    {
        public string Address { get; set; }
    }
    class X
    {
        public int Id { get; set; }
        public Employer Employer { get; set; }
    }
}

Open in new window


static void Main(string[] args)
        {
            A a = new A();
            a.Id =  1033;
            a.Employer = new Employer();
            a.Employer.Name = "Chinmay";
            a.Employer.House = new EmployerHouse();
            a.Employer.House.Address = "39";
            X x = new X();
            //x.Employer = new Employer();
            //x.Employer.House = new EmployerHouse();
            a.CopyProperties(x);
            Console.WriteLine(x.Employer.Name);
            Console.WriteLine(x.Employer.House.Address);
            Console.ReadKey();
        }

Open in new window


If you do not have instance of class X, you can use Activator.CreateInstance(typeof(x)) after that your method will take care of the rest.
Actually, I did something like this:

           Reflection.CopyProperties(A, B);
            B.Employer = new B.Employer[A.Employer.Length];
            A.Employer.CopyProperties(B.Employer);
 

Open in new window


However. when the CopyProperties runs source is showing the Employer nested object and the target is showing that it is created but the results from the LINQ is showing null so therefor nothing is happening from the FOREACH statement.

What am I missing here?
Hi Chinmay,

That is like what I had in the :

B.Employer = new B.Employer[A.Employer.Length];

Open in new window


But that didn't work either.
The LINQ is still returning a NULL even though the source is showing the Employer and the Destination is showing Employer even though empty because of the "new" yet after the LINQ executes the result is still showing null. How could that be?
What is there is more than 1 employer? So the Employer nested object there is more than 1?
List a list of x?

List<X> x = new List<X>(); 

Open in new window


That doesn't work. How could I get that to work?
Hi Davism,

Please share the entire class definition you are trying to clone. Please strip down any sensitive information that they might have.
I just ran my code against a List<Employer> scenario and it worked fine.

Please share the code you are using as well. Code snippets are not helping.

Regards,
Chinmay.
It's actually a third-party app and dll. I have it as a service reference for a WCF web service.

The application and DLL are centered around healthcare. That being said on the third-party aspect I am including a file that is used in the service reference. It's actually showing up as part the WSDL as Reference.cs but I changed the name on this.

You will notice that it is a "ClaimService5", The other DLL's  are like ClaimService5_1, ClaimService5_2, etc.  The structure and field names are the same per type (i.e. ClaimService5, ClaimService5_1, etc.

So, you see in like in the CommonClaim partial class (starting on line 1218) is the primary of what I am trying to copy.

You notice that is starts the nested or sub object of like CommonClaimDetail on line 1451. There are several nested objects in the CommonClaim (i.e. CommonConditiionCode,  CommonProcedureCode, etc)

Now when I do the:

Reflection.CopyProperties(req.ClaimInfo, clm);

Where ClaimInfo = CommonClaim.

I get everything copied but when it gets to those nested or sub objects like the CommonClaimDetails it copies nothing. The req.ClaimInfo (source information in the CommonClaimsDetails. The "clm" object which is the ClaimService5_4.CommonClaim (i.e. Destination) has the Details but it is null because nothing was copied and I haven't been able CommonClaimDetail information in it.

I even tried:

            foreach (var ClaimDetails in req.ClaimInfo.Details)
            {
                List<ClaimService5_4.CommonClaimDetail> clmDetail = new List<ClaimService5_4.CommonClaimDetail>();
                ClaimService5_4.CommonClaimDetail clmDetails = new ClaimService5_4.CommonClaimDetail();
                ClaimDetails.CopyProperties(clmDetails);
                clmDetail.Add(clmDetails);
                //clmDetail = clm.Details.ToList();
            }

Open in new window


After were I did the initial Reflection.CopyProperties but that didn't work either.

I hope this makes a little more sense.

I look forward to any information you can provide on this.
TestMethods.cs
Mercury.WMCY.WMCYServices.ClaimServic.cs
If I take the CopyProperties out of the LINQ and have it check for like the "Details" which is related to the CommonClaimDetails[] is it possible to do a recursion in that?  Like where I have it checking for the srcProp.Name=="Details" below.

But how do I get the recursion source for the CommonClaimDetails in there as well as the destination CommonClaimDetails?

        /// <summary>
        /// Extension for 'Object' that copies the properties to a destination object.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="destination">The destination.</param>
        public static void CopyProperties(this object source, object destination)
        {
            // If any this null throw an exception
            if (source == null || destination == null)
                throw new Exception("Source or/and Destination Objects are null");
            // Getting the Types of the objects
            Type typeDest = destination.GetType();
            Type typeSrc = source.GetType();

            // Iterate the Properties of the source instance and  
            // populate them from their desination counterparts  
            PropertyInfo[] srcProps = typeSrc.GetProperties();
            foreach (PropertyInfo srcProp in srcProps)
            {
                if (!srcProp.CanRead)
                {
                    continue;
                }
                PropertyInfo targetProperty = typeDest.GetProperty(srcProp.Name);

                if (targetProperty == null)
                {
                    continue;
                }
                if (!targetProperty.CanWrite)
                {
                    continue;
                }
                if (targetProperty.GetSetMethod(true) != null && targetProperty.GetSetMethod(true).IsPrivate)
                {
                    continue;
                }
                if ((targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) != 0)
                {
                    continue;
                }
                if ((!targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType) && (!(srcProp.Name == "Details"))))
                {
                    continue;
                }
                else
                { }

                if (srcProp.Name == "Details")
                {
                     Recursion for each index in the array of the source CommonClaimDetails to the destination CommonClaimDetails. Or would it even have to be for each index or will it work with just the entire arraylist or list? 
                }

                // Passed all tests, lets set the value
                targetProperty.SetValue(destination, srcProp.GetValue(source, null), null);
            }
        }
    }

Open in new window

Hi davism,

I just converted my List<Employer> to Employer[] and it still worked. Maybe we are missing something else. If you want to see my code in action, I am more than happy to do a screen sharing session. It should work, I am not sure if there is something else in the Web Service's code that is breaking it. One thing I want you to check (in the original code- the one with LINQ, this simplified version I am not sure, if it is needed - but if it works for you ten I am fine as well) whether you get Details property in the results or not? If not, then we need to focus on the criteria which we are using to filter out properties to be copied.

Also the simplified code above is not equivalent to the one with LINQ. It is still checking for Destination property and the SetValue method is messed up as it does not have the necessary flags to create the instance.

Also, we do not need to use recursion as far as I know. If there are multiple members as long as they are same data types, it will be copied.

Another point I would want to clarify, in my code, class Employer is common between class A and class X, and so it is in your case as well.
public Mercury.WMCY.WMCYServices.ClaimService5.CommonClaimDetail[] Details is common between all the other classes you want to clone. Please confirm this is a correct understanding.

Regards,
Chinmay.
Hi Chinmay,

Yeah...I think the "common" reference you have on the Employer what is a little different especially when you look at the structure layout that's why I don't think along the same lines as what is going on. FYI-->I did get it to work but I was changing it to try and be the same type of situation and that's where I wasn't getting it to work.

In the structure and partial class that I provided, the Mercury.WMCY.WMCYServices.ClaimService5.CommonClaimDetail[]  is part of the Mercury.WMCY.WMCYServices.ClaimService5.CommonClaim. The ClaimService5_1 has a Mercury.WMCY.WMCYServices.ClaimService5_1.CommonClaimDetail[]  is part of the Mercury.WMCY.WMCYServices.ClaimService5_1.CommonClaim, as does ClaimService5_2 with Mercury.WMCY.WMCYServices.ClaimService5_2.CommonClaimDetail[]  is part of the Mercury.WMCY.WMCYServices.ClaimService5_2.CommonClaim, etc.

What I am trying to do is take the object properties (the data) in the object and copy it to one of the others like Mercury.WMCY.WMCYServices.ClaimService5_4.CommonClaimDetail[]  is part of the Mercury.WMCY.WMCYServices.ClaimService5_4CommonClaim

So, no, it is not common. That's where I was thinking recursion may be necessary. And with the simple (i.e. non-LINQ CopyProperties came in because I wanted to look at it step-by-step.
Oh you can do the same with LINQ as well - the step by step, just comment out each line as you progress - but I leave it to you. To tell you the truth, I am personally not a big fan of LINQ - I think I have control issues ;)

Now to answer the question at hand, what you are suggesting are different classes, I think the last line, is assignable might be failing for us. These are two different classes altogether. Their structure might be the same but they are still different.

So, 1. .PropertyType.IsAssignableFrom(srcProp.PropertyType) will fail. It will not add the class to the result set. Now if you comment it out, then props.targetProperty.SetValue(destination, props.sourceProperty.GetValue(source, null), BindingFlags.CreateInstance, null, null, null); will fail as the classes won't match.

Now, we could fight this differently,

            foreach (var props in results)
            {
                if (props.targetProperty.Name.Equals("Employers"))
                {
// We might have to tweak this a bit, maybe write a dedicated processor for Details class as we are dealing with anonymous type
                    CopyProperties(props.sourceProperty, props.targetProperty);
                }
                else
                {
                    props.targetProperty.SetValue(destination, props.sourceProperty.GetValue(source, null), BindingFlags.CreateInstance, null, null, null);
                }
            }

Open in new window


 Do you have other such classes where this functionality is needed or is it just Details?
Let me give that a shot but yes there are other classes in there as mentioned in a previous post:

"You notice that is starts the nested or sub object of like CommonClaimDetail on line 1451. There are several nested objects in the CommonClaim (i.e. CommonConditiionCode,  CommonProcedureCode, etc) "

But let me give that a shot on what you just posted.
Actually that didn't work.  It didn't error but it also didn't copy them over:

            foreach (var props in results)
            {
                if ((props.targetProperty.Name.Equals("Details")) || (props.targetProperty.Name.Equals("ConditionCodes")) || (props.targetProperty.Name.Equals("DiagnosisCodes")) || (props.targetProperty.Name.Equals("OccurenceCodes"))
                    || (props.targetProperty.Name.Equals("ProcedureCodes")) || (props.targetProperty.Name.Equals("ValueCodes")) || (props.targetProperty.Name.Equals("ClaimProviders")))
                {
                    // We might have to tweak this a bit, maybe write a dedicated processor for Details class as we are dealing with anonymous type
                    CopyProperties(props.sourceProperty, props.targetProperty);
                }
                else
                {
                    props.targetProperty.SetValue(destination, props.sourceProperty.GetValue(source, null), BindingFlags.CreateInstance, null, null, null);
                }
            }

Open in new window


It commented out that IsAssignableFrom line and left the destination as a null.

As seen in Capture.pngUser generated image
But you can see it in the source where it is populated:

User generated image
Yes. i was trying various combinations as well - I am hitting a brick wall with Arrays. I think we might have to create a separate method to process arrays. Direct approach will not work in this case.
What are you thinking on that because I tried a separate aspect with a foreach after the:

Reflection.CopyProperties(req.ClaimInfo, clm);

But that didn't seem to associate to the "clm" which is why I thought it had to be in that same Reflection routine.
I guess how  can it be connected with the "clm"? What ways do we have to do that?

And then I just realized that in the CommonClaimDetail there is another nested object in it which is CommonPharmacyCode
Or is there another option using .NET Framework 4.0 and not using Reflection? Just curious because there has been some time-spent and it seems close but so far way. I am wondering if there is another option.

Or are there still possibilities with Reflection?
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
What's the definition of Employer look like in your Person class? Is it private? Public? Is it a field or a property?
Craig! Are you kidding me!!!!!

THAT WORKED with the newsonsoft.json!!!!!!

I thought about that before but I though the serialization/deserialization from when I did it didn't work but this worked. Maybe even the .NET with the binaryformatter was the problem albeit I saw several references of coping objects didn't. As such, I never even thought about trying the json...go figure...I should have!
Yeh binary formatter embeds the type information into the serialised bytes. Json formatter doesn't so it's type agnostic :)
All, thank you very, very much! I appreciate attempts with reflection Chinmay! I NuGet package did the trick. I know a form of flattening out the object and bring it back would be as way. I though Reflection would have done that easily. But what seems daunting sometimes is simply done and that is what the Nuget package showed. But thanks guys and very, very much appreciated!