In a testing scenario, I think the best option is to use hbm2ddl to recreate the database before starting. That isn't always an option if you're starting from an existing schema, though.
Here's a really hacky solution that doesn't us NH per se, but does expoit the mapping metadata to get at information about an implied table clearing ordering:
Code:
/// <summary>A dependency chain for deleting objects.</summary>
/// <remarks>Records which loaded objects are dependent on each other so the tables can
/// be automatically cleaned out at the end of a test fixture. These aren't explicit
/// dependencies, really, just a failsafe table clearing order that won't interfere with
/// database DRI.
/// <para>
/// Detects dependencies within an <see cref="NHibernate.Cfg.Configuration"/>.
/// </remarks>
public class DeletionChain {
private static log4net.ILog log = log4net.LogManager.GetLogger( System.Reflection.MethodBase.GetCurrentMethod().DeclaringType );
private NHibernate.Cfg.Configuration cfg;
private IList mainClassChain;
private IList leafTableChain;
// private IList chainedClasses;
#region ctor
public DeletionChain(NHibernate.Cfg.Configuration configuration) {
cfg = configuration;
mainClassChain= new ArrayList();
leafTableChain = new ArrayList();
// chainedClasses = new ArrayList();
BuildChain();
}
#endregion
#region Build
private void BuildChain() {
log.Info("Building table dependency chain");
foreach (PersistentClass pc in cfg.ClassMappings)
this.AddClass( pc );
}
private int level = 0;
private void AddClass(PersistentClass pc) {
level++;
string levelSpace = string.Empty.PadRight( level );
if (pc.IsMutable && (pc is RootClass) && !mainClassChain.Contains(pc)) {
log.DebugFormat( "{0}Inspecting {1}", levelSpace, pc.Name );
foreach(Property prop in pc.SubclassPropertyClosureCollection) {
if ((prop.Value is Collection) && !leafTableChain.Contains( ((Collection)prop.Value).CollectionTable )
&& !((Collection)prop.Value).IsInverse
) {
log.DebugFormat( "{0}Adding collection table {1}", levelSpace, ((Collection)prop.Value).CollectionTable.Name);
leafTableChain.Add( ((Collection)prop.Value).CollectionTable );
}
if ( (prop.Value is ToOne) && prop.IsInsertable ) // HACK: there still could be a dependency, but for now...
AddClass( cfg.GetClassMapping( prop.Value.Type.ReturnedClass ).RootClazz );
}
log.DebugFormat( "{0}Adding {1}", levelSpace, pc.Name);
mainClassChain.Add(pc);
}
else if (log.IsDebugEnabled) {
string reason = null;
if (!pc.IsMutable) reason = "immutable";
else if (pc is Subclass) reason = "subclass";
else if ( mainClassChain.Contains( pc ) ) reason = "already chained";
else reason = "unknown!";
log.DebugFormat( "{2}...skipping {0} ({1})", pc.Name, reason, levelSpace );
}
level--;
}
#endregion
#region Pop & Peek
/// <summary>Gets the next "leaf" table and "pops" it off the chain.</summary>
/// <returns>A NHibernate <see cref="NHibernate.Mapping.Table"/>.</returns>
public Table Pop() { return popPeek( true ); }
/// <summary>Gets the next "leaf" table and leaves in on the chain.</summary>
/// <returns>A NHibernate <see cref="NHibernate.Mapping.Table"/>.</returns>
public Table Peek() { return popPeek( false ); }
private Table popPeek(bool pop) {
Table link = null;
int leafCount = leafTableChain.Count;
int mainCount = mainClassChain.Count;
if (leafCount > 0) {
link = (Table)leafTableChain[ leafCount - 1 ];
if (pop) leafTableChain.RemoveAt( leafCount - 1 );
}
else if (mainCount > 0) {
link = ((PersistentClass)mainClassChain[ mainCount - 1 ]).Table;
if (pop) mainClassChain.RemoveAt( mainCount - 1 );
}
return link;
}
#endregion
}
To use, just construct and call Build(). Then, after you're done testing (or before you start) Pop() each table off the chain, and send a
Code:
"delete " + table.Name
to the db.
caveat emptor. This remains untested outside my own use. Modifications may be necessary to make it usable for you.