Blogs

Gary's Blog

XAF – Cloning Objects #2

     

In a previous post, on cloning objects in XAF, I showed you how to use the Clone Module. Of course, this solution only works if there is user interaction. What if you want to do this programmatically? Well, given the following business class:

using System;
using DevExpress.Xpo;

namespace SimpleObject {
    public class Parent : XPObject {
        public Parent(Session session) : base(session) { }
        public override void AfterConstruction() {
            base.AfterConstruction();
        }

        private string _Name;
        public string Name {
            get {
                return _Name;
            }
            set {
                SetPropertyValue("Name", ref _Name, value);
            }
        }

        [Association("ParentChildren"), Aggregated]
        public XPCollection<Child> Children {
            get { return GetCollection<Child>("Children"); }
        }
    }

    public class Child : XPObject {
        public Child(Session session) : base(session) { }
        public override void AfterConstruction() {
            base.AfterConstruction();
        }

        private string _Name;
        public string Name {
            get {
                return _Name;
            }
            set {
                SetPropertyValue("Name", ref _Name, value);
            }
        }

        private Parent _Parent;
        [Association("ParentChildren")]
        public Parent Parent {
            get {
                return _Parent;
            }
            set {
                SetPropertyValue("Parent", ref _Parent, value);
            }
        }
    }
}

And the following helper class:

using System;
using DevExpress.Xpo;
using System.Collections.Generic;
using DevExpress.Xpo.Metadata;
using System.Collections;

namespace SimpleObject {
    public class CloneIXPSimpleObjectHelper {
        /// <summary>
        /// A dictionary containing objects from the source session as key and objects from the 
        /// target session as values
        /// </summary>
        /// <returns></returns>
        Dictionary<object, object> clonedObjects;
        Session sourceSession;
        Session targetSession;

        /// <summary>
        /// Initializes a new instance of the CloneIXPSimpleObjectHelper class.
        /// </summary>
        public CloneIXPSimpleObjectHelper(Session source, Session target) {
            this.clonedObjects = new Dictionary<object, object>();
            this.sourceSession = source;
            this.targetSession = target;
        }

        public T Clone<T>(T source) where T : IXPSimpleObject {
            return Clone<T>(source, targetSession, false);
        }
        public T Clone<T>(T source, bool synchronize) where T : IXPSimpleObject {
            return (T)Clone(source as IXPSimpleObject, targetSession, synchronize);
        }

        public object Clone(IXPSimpleObject source) {
            return Clone(source, targetSession, false);
        }
        public object Clone(IXPSimpleObject source, bool synchronize) {
            return Clone(source, targetSession, synchronize);
        }

        public T Clone<T>(T source, Session targetSession, bool synchronize) where T : IXPSimpleObject {
            return (T)Clone(source as IXPSimpleObject, targetSession, synchronize);
        }

        /// <summary>
        /// Clones and / or synchronizes the given IXPSimpleObject.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="targetSession"></param>
        /// <param name="synchronize">If set to true, reference properties are only cloned in case
        /// the reference object does not exist in the targetsession. Otherwise the exising object will be
        /// reused and synchronized with the source. Set this property to false when knowing at forehand 
        /// that the targetSession will not contain any of the objects of the source.</param>
        /// <returns></returns>
        public object Clone(IXPSimpleObject source, Session targetSession, bool synchronize) {
            if (source == null)
                return null;
            if (clonedObjects.ContainsKey(source))
                return clonedObjects[source];
            XPClassInfo targetClassInfo = targetSession.GetClassInfo(source.GetType());
            object clone = null;
            if (synchronize)
                clone = targetSession.GetObjectByKey(targetClassInfo, source.Session.GetKeyValue(source));
            if (clone == null)
                clone = targetClassInfo.CreateNewObject(targetSession);
            clonedObjects.Add(source, clone);

            foreach (XPMemberInfo m in targetClassInfo.PersistentProperties) {
                if (m is DevExpress.Xpo.Metadata.Helpers.ServiceField || m.IsKey)
                    continue;
                object val;
                if (m.ReferenceType != null) {
                    object createdByClone = m.GetValue(clone);
                    if ((createdByClone != null) && synchronize == false)
                        val = createdByClone;
                    else {
                        val = Clone((IXPSimpleObject)m.GetValue(source), targetSession, synchronize);
                    }

                }
                else {
                    val = m.GetValue(source);
                }
                m.SetValue(clone, val);
            }
            foreach (XPMemberInfo m in targetClassInfo.CollectionProperties) {
                if (m.HasAttribute(typeof(AggregatedAttribute))) {
                    XPBaseCollection col = (XPBaseCollection)m.GetValue(clone);
                    XPBaseCollection colSource = (XPBaseCollection)m.GetValue(source);
                    foreach (IXPSimpleObject obj in new ArrayList(colSource))
                        col.BaseAdd(Clone(obj, targetSession, synchronize));
                }
            }
            return clone;
        }
    }
}

Then cloning can be performed using the following code:

private void Form1_Load(object sender, System.EventArgs e) {
     UnitOfWork uow1 = new UnitOfWork();
     Parent obj = new Parent(uow1);
     obj.Name = "Parent A";
     Child ch1 = new Child(uow1);
     ch1.Name = "Child 1";

     Child ch2 = new Child(uow1);
     ch2.Name = "Child 2";

     obj.Children.Add(ch1);
     obj.Children.Add(ch2);
     obj.Save();
     uow1.CommitChanges();

     listBox1.Items.Add(string.Format("Object created: {0}", obj.Name));

     UnitOfWork uow2 = new UnitOfWork();
     CloneIXPSimpleObjectHelper cloneHelper = new CloneIXPSimpleObjectHelper(uow1, uow2);
     Parent clone = cloneHelper.Clone(obj) as Parent;

     listBox1.Items.Add(string.Format("Object cloned: {0}", clone.Name));
     listBox1.Items.Add(string.Format("Object's child 1: {0}", clone.Children[0].Name));
     listBox1.Items.Add(string.Format("Object's child 2: {0}", clone.Children[1].Name));
 }
Digg This
Published Jan 12 2009, 03:36 PM by Gary Short (DevExpress)
Filed under:
Technorati tags: XAF
Bookmark and Share

Comments

 

Jascha said:

Hi Gary,

How does this differ to using DevExpress.Persistent.Base.Cloner?

Cheers,

Jascha

January 12, 2009 12:10 PM
 

Gary Short (DevExpress) said:

Jascha, that class is only used internally and so is not documented, therefore you might not want to use it.

January 13, 2009 9:53 AM
 

Alex Hoffman said:

This phrase "class is only used internally and so is not documented - so don't use it" seems to be an increasingly common excuse.  The Support Center is full of examples of using the Cloner class.

If you do not want it to be used, please mark the class non-public.  

Otherwise, please document it.  Isn't this an application framework designed to be extensible?

January 13, 2009 4:30 PM
 

Hans Hansen said:

Hi Gary,

Please check your code.

This line makes the whole Clone methoed useless.

           if (synchronize)

               clone = targetSession.GetObjectByKey(targetClassInfo, source.Session.GetKeyValue(source));

If synchronize has true, you'll always receive reference to same object (I'm don't talking about property) you want to clone. So even shallow copy won't be possible.

Regards,

Alexey

August 27, 2009 4:31 AM
More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 8:30am and 5:00pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.