My client has come back to me with a list of enhancements they need implemented on their application. Among those requirements is the ability to take an existing Project, the top-level business object, creating a copy of it, modifying a few values such as the job number, and saving it to the database. I'd like to use something like the Clone method, but it won't allow me to assign a new Guid as the project ID and, without a new ID, it causes a collision with the original object when trying to save it to the database.
I've searched both this forum and the original one and just can't wrap my mind around the process.
Depending upon what you are using for a database back-end, it might be preferable to just code the logic in a stored procedure in the database and invoke that from your Clone() method.
Pluses:
Reduced network traffic
Reduced load on the application server.
Probably run way faster if it's done in SQL using a set-based approach rather than a procedural, record-based approach.
Less chance of a timeout in a web app if the call is done asynchronously.
Minuses:
Increased load on the database server. (Maybe! If more efficiently written inside the database, it may require less database effort!)
Requires more SQL and T-SQL (or PL/SQL, etc.) knowledge.
Unknown:
Haven't worked with guids yet, so I don't actually know if you can set them from inside the database. (Pretty sure you can do it from SqlServer 2005, but don't know if there are any performance issues.) If you only need one (for the master project record) you could pass it in...
RockfordLhotka:In CSLA .NET 2.0, the cloning method (GetClone()) is virtual/Overridable, so you can do this using Clone().
The drawback to this approach is that the original object may have fields that aren't exposed as properties, and you'd need to use reflection to copy them.
Hi albruan:
Use an interface that looks like this:
public interface CloneableIdentity
{
bool CanCloneIdentity { get; }
object CloneNewIdentity(object parentKey);
}
[Serializable()]
public class ProjectBusinessBase : BusinessBase, CloneableIdentity
{
//
# region CloneableIdentity Members
public virtual bool CanCloneIdentity
{
get { return false; }
}
object CloneableIdentity.CloneNewIdentity(object parentKey)
{
if(!CanCloneIdentity)
throw new InvalidOperationException();
ProjectBusinessBase clone = Clone() as ProjectBusinessBase;
clone.MarkNew();
clone.ChangeInfoForIdentityClone(parentKey);
return clone;
}
protected virtual void ChangeInfoForIdentityClone(object parentKey)
{
}
#endregion //--CloneableIdentity Members
}
[Serializable()]
public class SalesOrder : ProjectBusinessBase
{
//
# region CloneableIdentity Members
public override bool CanCloneIdentity
{
get { return (IsClosed || IsAllocated) && !IsDirty && IsValid; }
}
protected override void ChangeInfoForIdentityClone(object parentKey)
{
SetKey(Guid.NewGuid());
for (int index = m_Items.Count - 1; index >= 0; index--)
m_Items[index] = ((CloneableIdentity)m_Items[index]).CloneNewIdentity(m_Key) as SalesOrderItem;
m_DateClosed = DateHelper.GetNullDate();
}
#endregion //--CloneableIdentity Members
}
[Serializable()]
public class SalesOrderItem : ProjectBusinessBase
{
//
# region CloneableIdentity Members
public override bool CanCloneIdentity
{
get { return true; }
}
protected override void ChangeInfoForIdentityClone(object parentKey)
{
SetKey(Guid.NewGuid());
m_ParentKey = parentKey;
}
#endregion //--CloneableIdentity Members
}
In the UI, have a Clone or Copy button on your base form, and do something like this:
public class BaseForm : Form
{
public BaseForm()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
if (HasDataSource && DataSource is CloneableIdentity &&
((CloneableIdentity)DataSource).CanCloneIdentity)
{
m_BtnCopy.Enabled = true;
}
}
}
In the form code, you would probably want to bind the Enabled property of the Copy button to the CanCloneIdentity property so that it will enable and disable as the CanCloneIdentity property changes, as it could in the case of our contrived SalesOrder example.
Also as far as collections go, it's a good idea to have them implement the CloneableIdentity interface as well, then you can just say "m_Items = (BusinessCollection<SalesOrderItem>)m_Items.CloneNewIdentity(m_Key);", and ensure that the collection "cleans itself up" by quiescently removing items from its DeletedList, etc. After all, you don't want to accidentally delete the items in the deleted list from the database when you save the brand spanking new SalesOrder. Because of the side effects of adding, removing or setting items in a collection, it's always a good idea to provide an explicit method (preferably defined in a special-purpose interface), and call that instead of just using the standard collection mutators (Add, Remove, indexed set, etc.)
This example is a bit contrived and incomplete because I haven't used CSLA for awhile and don't know the current state of the art, but I have used a pattern like this for literally years with great success.
Here's a picture of what mine looks like in action.
Good luck,
--Bruce
Hi Bruce,
I've been seriously looking at using such an interface since seeing your posting in the old forum at http://groups.msn.com/CSLANET/general.msnw?action=get_message&mview=0&ID_Message=21957&LastModified=4675545836911824873
I have a few questions about using the interface as described in your post at the old forum and here and they are as follow:
Thanks for taking the time to answer these questions.
BTW, I like your name...my middle name is Bruce, hence the "bru" portion of my username here.
Allen
Hi Allen:
I have to take off on a field trip for my kids in a few minutes here, but I'll get back to your questions sometime this weekend or Monday.
--Bruce
Copyright (c) Marimer LLC