4

I have an MVC3 application that needs to sync view models with database models. I found myself writing way too much code to copy properties back and forth between the different objects. I avoided this where I could simply subclass the data model, but at other times, I found that too restricting.

I developed a few extension methods on Object to support shallow cloning of properties with similar names, and this has worked rather well. However, I'm wondering if there is a more efficient means to accomplish the same thing. So I guess this is asking for peer review and options to improve upon this code.

UPDATE: I've found it better to handle related tables explicitly. Testing for IsVirtual will prevent relations from being inadvertently affected during cloning. See updated CloneMatching method. The others explicitly state which properties to update or exclude.

public static class CustomExtensions
{
   public static T CloneMatching<T, S>(this T target, S source)
       where T : class
       where S : class
    {
        if (source == null)
        {
            return target;
        }
        Type sourceType = typeof(S);
        Type targetType = typeof(T);
        BindingFlags flags = BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance;

        PropertyInfo[] properties = sourceType.GetProperties();
        foreach (PropertyInfo sPI in properties)
        {
            PropertyInfo tPI = targetType.GetProperty(sPI.Name,flags);
            if (tPI != null && tPI.PropertyType.IsAssignableFrom(sPI.PropertyType) && !tPI.PropertyType.IsVirtual)
            {
                tPI.SetValue(target, sPI.GetValue(source, null), null);
            }
        }
        return target;
    }

    public static T CloneProperties<T, S>(this T target, S source, string[] propertyNames)
       where T : class
       where S : class
    {
        if (source == null)
        {
            return target;
        }
        Type sourceType = typeof(S);
        Type targetType = typeof(T);
        BindingFlags flags = BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance;

        PropertyInfo[] properties = sourceType.GetProperties();
        foreach (PropertyInfo sPI in properties)
        {
            if (propertyNames.Contains(sPI.Name))
            {
                PropertyInfo tPI = targetType.GetProperty(sPI.Name, flags);
                if (tPI != null && tPI.PropertyType.IsAssignableFrom(sPI.PropertyType))
                {
                    tPI.SetValue(target, sPI.GetValue(source, null), null);
                }
            }
        }
        return target;
    }
    public static T CloneExcept<T, S>(this T target, S source, string[] propertyNames)
       where T : class
       where S : class
    {
        if (source == null)
        {
            return target;
        }
        Type sourceType = typeof(S);
        Type targetType = typeof(T);
        BindingFlags flags = BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance;

        PropertyInfo[] properties = sourceType.GetProperties();
        foreach (PropertyInfo sPI in properties)
        {
            if (!propertyNames.Contains(sPI.Name))
            {
                PropertyInfo tPI = targetType.GetProperty(sPI.Name, flags);
                if (tPI != null && tPI.PropertyType.IsAssignableFrom(sPI.PropertyType))
                {
                    tPI.SetValue(target, sPI.GetValue(source, null), null);
                }
            }
        }
        return target;
    }
}

Here is an example of how I use it to map a view model to the data model.

DataSession.Quote.CloneProperties(viewModel,
                new[] {"PaymentType","CardHolder","CardHolderZip","CardNumber","CardExp","CVC",
                          "AccountHolder","AccountHolderZip","ABA","Account",
                          "AccuracyAgreement","PrivacyTermsAgreement","ElectronicSignatureAgreement"});
B2K
  • 2,541
  • 1
  • 22
  • 34
  • 3
    Would AutoMapper or something similar not help you with this. http://automapper.org – Quinton Bernhardt Jan 08 '13 at 16:01
  • This is the approach we use to solve the situation of OP. +1 – Casper Leon Nielsen Jan 08 '13 at 16:10
  • That would certainly do what I want. How is the performance? I skimmed the code for it, and it seems a bit overkill for what I need. – B2K Jan 08 '13 at 16:36
  • @quinton Looking at Automapper's documentation, it seems they don't have a clean way to clone specific properties on a large object. I've revised my question above to demonstrate this. – B2K Jan 08 '13 at 16:54
  • 1
    i'm not an expert on automapper. this is an old post but perhaps it may sched some light else maybe post another question. http://stackoverflow.com/a/6474397/1099260 – Quinton Bernhardt Jan 08 '13 at 18:01

1 Answers1

1

You may consider using Object.MemberwiseClone method.

The MemberwiseClone method creates a shallow copy by creating a new object, and then copying the nonstatic fields of the current object to the new object. (docs.microsoft)

The method is protected. It means that you have to implement the Clone method in your classes. To make things even more fance you can add ICloneable to your classes as well.

class MyClass: ICloneable
{
    // all your code
    MyClass Clone()
    {
        return (MyClass)this.MemberwiseClone();
    }
}
Jakub Šturc
  • 35,201
  • 25
  • 90
  • 110
  • This returns an object of the same type as the original. What I want is to clone properties of the same name across objects of different types. – B2K Sep 06 '19 at 22:03
  • Consider this page, where a view model encapsulates a person, vehicle, and driving history information. On the back end, these are split to individual entities, but presented to the user as a single form. https://demoagent.com/applicant – B2K Sep 06 '19 at 22:09