Answer:
You may have wondered why clearing a PM does not clear all EntityLists of the entities that live in that PM.
You've likely experienced this when a control or grid bound to an EntityList with entities in the cleared PM crashes.
Removing an entity from the PM does remove it from any EntityList that holds it, whether or not that EntityList is a managed list. All EntityLists remove detached entities when they see them become detached; you don't have to add a ListManager to enjoy this effect.
However, clearing the PM (which turns all its entities into detached entities) does not cause the EntityList to update itself.
The attached nunit test code demonstrates that fact and shows how to work around it (see ClearAllEntities).
We want to keep the current PM behavior because it is fast. An app that wants to clear a PM (rather than discard it) generally knows its EntityLists and can clear (or discard) them when it clears a PM. However, some apps are not designed to be sure that they know all of the EntityLists dependent on the PM.
We have a feature request (as of January 2007) to provide an alternative to PersistenceManager.Clear() that will achieve this purpose more efficiently.
UNIT TEST CODE AND WORKAROUND
/// <summary>Test that Clearing a PM also clears all EntityLists.</summary>
[Test]
public void PmClearTest() {
PersistenceManager pm = new PersistenceManager(PersistenceManager.DefaultManager);
// Read some entities so we can see what we're clearing;
EntityList<Customer> custList = pm.GetEntities<Customer>();
EntityList<Order> orderList = pm.GetEntities<Order>();
EntityList<Employee> empList = new EntityList<Employee>();
empList.ListChanged += new ListChangedEventHandler(empList_ListChanged); // listen for entities coming and going
msListChangedContext = "initialize emplist"; // context in which list changed
empList.ReplaceRange(pm.GetEntities<Employee>());
int originalCount = empList.Count;
Assert.Greater(originalCount, 0, "Got no emps.");
msListChangedContext = "remove emp";
Employee anEmp = empList[0];
pm.RemoveEntity(anEmp, false);
Assert.AreEqual(anEmp.RowState, DataRowState.Detached, "Removed emp is not detached.");
Assert.AreEqual(originalCount - 1, empList.Count, "Should have had one fewer emp after remove");
anEmp.AddToManager();
msListChangedContext = "re-add emp to manager and then to list";
empList.Add(anEmp);
// Defect: I think pm.Clear should be able to clear all EntityLists that it knows about automatically.
// Because it doesn't, we use ClearAllEntities to get the job done.
// Comment out the following 2 lines to see that PM doesn't clear the EntityLists and the tests fail.
msListChangedContext = null; // Don't report list changed for as its items disappear individually
ClearAllEntities(pm); // Clear the PM entities first
msListChangedContext = "clear pm."; // Never shown because no ListChanged event on Pm.Clear()
pm.Clear();
Assert.IsTrue(empList.Count == 0, "Have emps in EntityList after PM clear.");
Assert.IsTrue(custList.Count == 0, "Have customers in EntityList after PM clear.");
Assert.IsTrue(orderList.Count == 0, "Have orders in EntityList after PM clear.");
}
#region PmClearTest Diagnostic
private static string msListChangedContext = "Unknown context";
static void empList_ListChanged(object sender, ListChangedEventArgs e) {
if ( String.IsNullOrEmpty(msListChangedContext) ) return;
Console.WriteLine("ListChanged fired in context: " + msListChangedContext);
}
#endregion
#region ClearAllEntities
/// <summary>Clears all entities in the Pm, also clearing associated EntityLists.</summary>
/// <param name="pPm">The PersistenceManager we will clear.</param>
/// <remarks>
/// This method is useful when we want to clear a PersistenceManager (PM)
/// and do not know what EntityLists contain entities currently resident in that PM.
/// <para>
/// An EntityList(Of T) cannot clear itself today
/// when we clear the PM holding entities in that list.
/// An EntityList only listens for changes to the rowstate of individual entities,
/// not to the EntityTable.TableCleared event.
/// Moreover, PersistenceManager.Clear does not clear individual EntityTables;
/// rather it simply discards the tables themselves.
/// </para>
/// <para>
/// This method iterates through all of the EntityTables of the PM
/// and removes each entity of each table.
/// </para>
/// <para>
/// This could be very slow; it would be better to keep track of our
/// EntityLists and clear them before clearing the PM.
/// However, it may not be possible to know all of the EntityLists with
/// entities in the PM we are clearing.
/// This method will take care of that because all EntityLists
/// will "hear" our removal of the individual entities.
/// </para>
/// </remarks>
private void ClearAllEntities(PersistenceManager pPm) {
pPm.QueryCache.Clear(); // Query cache should be empty after this operation
DataTableCollection pEntityTables = pPm.DataSet.Tables;
foreach ( DataTable anEntityTable in pEntityTables ) {
ClearEntityTable(anEntityTable);
}
}
/// <summary>Clears the entities of the type, T, in the Pm, also clearing associated EntityLists.</summary>
/// <typeparam name="T">The entity type to clear.</typeparam>
/// <param name="pPm">The PersistenceManager holding instances of this Entity type.</param>
private void ClearEntityType<T>(PersistenceManager pPm) where T : Entity {
EntityTable table = pPm.GetTable(typeof(T));
ClearEntityTable(table);
}
/// <summary>Clears the entities in the Entity table.</summary>
private static void ClearEntityTable(DataTable pEntityTable) {
// Diagnostic:
Console.WriteLine("Clearing EntityTable: " + pEntityTable.TableName);
// pEntityTable.Clear(); // doesn't work
// must remove each entity individually
DataRowCollection entities = pEntityTable.Rows;
for ( int i = ( entities.Count - 1 ); i >= 0; i-- ) {
entities.RemoveAt(i);
}
}
#endregion