Scenario
In this example, we create a CloneHelper class that will be used to clone the provided persistent object.
Steps to implement
1. Implement the CloneHelper class as shown in the CloneHelper.xx file. This class uses persistent object metadata to create a copy of the provided persistent object.
The Clone method has several overloads that allow you to determine whether object synchronization is required or not. If the synchronize parameter is set to true, reference properties are cloned only if the referenced object does not exist in the target Session. Otherwise, the existing object will be reused.
2. Create a Form that will use the ListBox control to display the result of creating and cloning persistent objects. See the code of the Form class in the Form1.xx file.
As a result, the following output will be shown:
See also:
How to get a list of a persistent object's properties
Should I use XPO to transfer data between databases?
Question Comments
Added By: Sigurd Decroos at: 1/8/2013 5:48:41 PM
There's a little catch with this code. Primary Keys are changed, so it doesn't make a real clone, but a copy. You have to change the code a bit so the primary key gets synced too if needed.
Added By: Mr292 at: 2/16/2013 12:45:27 PMI refactored some in order to replicate to different databases as well as to different assemblies in the other database. I came about this requirement because I wanted to remodel my DOM and did not want to have to manually do a lot of data imports.
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;
UnitOfWork targetSession;
/// <summary>
/// Initializes a new instance of the CloneIXPSimpleObjectHelper class.
/// </summary>
public CloneIXPSimpleObjectHelper(Session source, UnitOfWork target)
{
clonedObjects = new Dictionary<object, object>();
sourceSession = source;
targetSession = target;
}
/// <summary>
/// Initializes a new instance of the CloneIXPSimpleObjectHelper class to Clone to Different DB.
/// </summary>
public CloneIXPSimpleObjectHelper(Session session, string connstring)
{
UnitOfWork NewUOW = new UnitOfWork(
XpoDefault.GetDataLayer(connstring, DevExpress.Xpo.DB.AutoCreateOption.SchemaAlreadyExists));
clonedObjects = new Dictionary<object, object>();
sourceSession = session;
targetSession = NewUOW;
}
/// <summary>
/// Initializes a new instance of the CloneIXPSimpleObjectHelper class to Clone to Different Assembly / DB.
/// </summary>
public CloneIXPSimpleObjectHelper(Session session, string connstring, System.Reflection.Assembly assembly)
{
var dict = new ReflectionDictionary();
foreach (var item in assembly.GetTypes())
{
if (item.IsSubclassOf(typeof(XPBaseObject)))
dict.CollectClassInfos(item);
}
UnitOfWork NewUOW = new UnitOfWork(
XpoDefault.GetDataLayer(connstring, dict,
DevExpress.Xpo.DB.AutoCreateOption.SchemaAlreadyExists));
clonedObjects = new Dictionary<object, object>();
sourceSession = session;
targetSession = NewUOW;
}
public void Commit()
{
targetSession.CommitTransaction();
}
public T Clone<T>(T source) where T : IXPSimpleObject
{
return Clone<T>(source, targetSession, CloneMethod.IgnoreIfExistsInDesitnation);
}
public T Clone<T>(T source, CloneMethod sync) where T : IXPSimpleObject
{
return (T)Clone(source as IXPSimpleObject, targetSession, sync);
}
public object Clone(IXPSimpleObject source)
{
return Clone(source, targetSession, CloneMethod.IgnoreIfExistsInDesitnation);
}
public object Clone(IXPSimpleObject source, CloneMethod sync)
{
return Clone(source as IXPSimpleObject, targetSession, sync);
}
public object Clone(IXPSimpleObject source, Session targetSession, CloneMethod sync)
{
return Clone(source as IXPSimpleObject, targetSession, sync);
}
public T Clone<T>(T source, UnitOfWork targetSession, CloneMethod sync) where T : IXPSimpleObject
{
return (T)Clone(source as IXPSimpleObject, targetSession, sync);
}
public object Clone(XPBaseObject obj, Type targetType, CloneMethod sync)
{
return Clone(obj, targetSession, targetSession.GetClassInfo(targetType), sync);
}
public enum CloneMethod
{
TransferOnly,
Synchronize,
IgnoreIfExistsInDesitnation
}
/// <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, UnitOfWork parentSession, XPClassInfo cloneClassInfo, CloneMethod sync)
{
if (source == null)
return null;
object sourceKey = source.Session.GetKeyValue(source);
if (clonedObjects.ContainsKey(source))
return parentSession.GetObjectByKey(cloneClassInfo,cloneClassInfo.KeyProperty.GetValue(clonedObjects[source]));
NestedUnitOfWork nestedSession = parentSession.BeginNestedUnitOfWork();
IXPSimpleObject clone = null;
if (sync != CloneMethod.TransferOnly)
{
clone = (IXPSimpleObject)nestedSession.GetObjectByKey(cloneClassInfo, sourceKey);
if (clone != null)
{
if (sync == CloneMethod.IgnoreIfExistsInDesitnation)
{
clonedObjects.Add(source, clone);
return nestedSession.GetParentObject(clone);
}
}
}
if (clone == null)
clone = (IXPSimpleObject)cloneClassInfo.CreateNewObject(nestedSession);
clonedObjects.Add(source, clone);
try
{
XPClassInfo sourceClassInfo = source.Session.GetClassInfo(source.GetType());
if (sourceClassInfo.KeyProperty.GetType() == cloneClassInfo.KeyProperty.GetType())
cloneClassInfo.KeyProperty.SetValue(clone, sourceKey);
foreach (XPMemberInfo cloneMember in cloneClassInfo.PersistentProperties)
{
XPMemberInfo sourceMem = sourceClassInfo.GetMember(cloneMember.Name);
if (sourceMem == null
|| cloneMember is DevExpress.Xpo.Metadata.Helpers.ServiceField
|| cloneMember.IsKey)
continue;
object val = null;
if (cloneMember.ReferenceType != null)
{
object createdByClone = cloneMember.GetValue(clone);
if (createdByClone != null)
{
val = Clone((IXPSimpleObject)sourceMem.GetValue(source),
nestedSession,
nestedSession.GetClassInfo(cloneMember.MemberType),
sync==CloneMethod.TransferOnly?CloneMethod.Synchronize:sync);
}
else
if ((cloneMember.IsAggregated)
//REMOVE Below LINE IF YOU WANT TO MAINTAIN ONLY ONE WAY AGGREGATIOn
|| (cloneMember.IsAssociation
&& cloneMember.GetAssociatedMember().IsAggregated)
)
{
val = Clone((IXPSimpleObject)sourceMem.GetValue(source), nestedSession,
nestedSession.GetClassInfo(cloneMember.MemberType), sync);
}
}
else
{
val = sourceMem.GetValue(source);
}
if ((val != null) && !(val is DateTime && (DateTime)val == DateTime.MinValue))
cloneMember.SetValue(clone, val);
}
foreach (XPMemberInfo m in cloneClassInfo.CollectionProperties)
{
if (m.HasAttribute(typeof(AggregatedAttribute)))
{
XPBaseCollection col = (XPBaseCollection)m.GetValue(clone);
XPBaseCollection colSource = (XPBaseCollection)sourceClassInfo.GetMember(m.Name).GetValue(source);
foreach (IXPSimpleObject obj in new ArrayList(colSource))
{
XPClassInfo targetinfo = col.GetObjectClassInfo();
col.BaseAdd(
Clone(obj, nestedSession,
targetinfo, sync));
}
}
}
nestedSession.CommitTransaction(); //
clonedObjects[source]= clone;
return nestedSession.GetParentObject(clone);
}
catch
{
if (nestedSession.InTransaction)
nestedSession.RollbackTransaction();
}
return null;
}
}
Dear,
I face a real problem with your clonehelper. After implementing we recoginized that this helper will not only clone my recordset, it will also clone/create my relations.
So, for example i have a order management where i want to clone one order. But what happened is that the order will be cloned, but the underlying addresses (relation via keys) will be cloned, too. So, after cloning 10 orders, i have 10 times the same address in my address-masterdata ?
Any idea ?
Added By: Chris D at: 8/4/2015 10:50:22 AMTo avoid creating the underlying record, i changed
If m.ReferenceType IsNot Nothing And NONEW=1 Then....
where NONEW=0.
Could you please have a short look if this is the right solution ?
Added By: Alexey (DevExpress Support) at: 8/4/2015 11:14:43 PM Hello Chris,Yes, it is possible to comment this part of code to disable creating of additional records.Added By: Paul Kubb at: 1/11/2016 9:00:47 PM
Is this equivalent to Extra Clone Module?
Added By: Alexey (DevExpress Support) at: 1/12/2016 3:32:14 AMYou are correct. While the Clone Object Module is specific for an XAF application, this approach allows implementing the same functionality with XPO in other .NET applications.
Added By: Andrew Bingham 2 at: 1/14/2016 12:39:16 AMCan you clarify the position with this Example vs, DevExpress.Persistent.Base.Cloner class
In Q251308 6 years ago Dennis said "The DevExpress.Persistent.Base.Cloner is an internal class and is not documented. You may not want to use it in your code as well. This class is used by our.... "
Is this recommendation "may not want to use" still true?
If so why?
Hello Andrew ,
To process your recent post more efficiently, I created a separate ticket on your behalf: T334058: E804 Example vs, DevExpress.Persistent.Base.Cloner class. This ticket is currently in our processing queue. Our team will address it as soon as we have any updates.
When I use this class to clone with synch
QuoteHeader clone = new Cloner(unitOfWork).Clone<QuoteHeader>(entity, unitOfWork, true);
The clone is NOT a new object so is not saved on unitOfWork.CommitChanges()
I understand the code and why this is the case
HOWEVER - this is obviously not how a cloner should work - what am I doing wrong?
Added By: Alexey (DevExpress Support) at: 1/14/2016 3:44:24 AMI have moved this question to a separate ticket: T334156: E804, created object is not new. This ticket is currently in our processing queue. We will answer you as soon as we have any updates.
Added By: Kiryl Y. at: 5/25/2016 8:14:23 PM I believe there is an error in CloneProperty Sub:I think instead of[VB.NET]PrivateSub CloneProperty(ByVal memberInfo As XPMemberInfo, ByVal source AsObject, ByVal target AsObject, ByVal synchronize AsBoolean)If TypeOf memberInfo Is DevExpress.Xpo.Metadata.Helpers.ServiceField OrElse memberInfo.IsKey ThenReturnEndIfDim clonedValue AsObject = NothingIf memberInfo.ReferenceType IsNot NothingThenDim value AsObject = memberInfo.GetValue(source)If value IsNot NothingThen value = CloneValue(value, synchronize, False)EndIfElse clonedValue = memberInfo.GetValue(source)EndIf memberInfo.SetValue(target, clonedValue)EndSub
[VB.NET]value = CloneValue(value, synchronize, False)
should be
[VB.NET]clonedValue = CloneValue(value, synchronize, False)
Added By: Alexey (DevExpress Support) at: 5/25/2016 10:01:06 PM Thank you for pointing this. We will update this example.