/// <summary>
///
/// Extension method: UpgradeOpen<T>() Version 2
///
/// This is a refactoring of the original UpgradeOpen<T>()
/// extension method that can be useful for upgrading the
/// OpenMode of objects to OpenMode.ForWrite, and skipping
/// objects on locked layers.
///
/// The original version was abysmally-slow due to its
/// reliance on exceptions that are thrown each time an
/// an attempt is made to upgrade an entity on a locked
/// layer to OpenMode.ForWrite.
///
/// This version eliminates that problem using a caching
/// scheme that caches the ObjectIds of locked layers,
/// and uses it to avoid the potentially-large number of
/// execptions that plagued the original version.
///
/// In this refactored version, rather than an exception
/// being throw and caught for each entity on a locked
/// layer, only one exception is thrown for each locked
/// layer referenced by entities in the sequence.
///
/// This refactored version runs about 5x faster than
/// the original, depending on the number of entities
/// on locked layers, and the number of locked layers
/// referenced by entities in the sequence.
///
/// In a test involving 100,000k DBPoint entities, about
/// 1/3 of which were on 4 locked layers, the new version
/// performed ~4x faster than the original (both tested
/// with the same dataset).
///
/// -----------------------------------------------------
/// Upgrades the OpenMode of a sequence DBObjects to
/// OpenMode.ForWrite.
///
/// If includeObjectsOnLockedLayers is true, entities on
/// locked layers are upgraded and included. Otherwise,
/// entities on locked layers are not upgraded, and are
/// omitted from the result, and no exception is raised,
///
/// If includeObjectsOnLockedLayers is true, this method
/// requires an active transaction, or must be passed a
/// a transaction, which can be an OpenCloseTransaction,
/// if the objects were initially opened with same.
///
/// </summary>
public static IEnumerable<T> UpgradeOpen<T>( this IEnumerable<T> source,
bool includeObjectsOnLockedLayers = false,
Transaction tr = null ) where T : DBObject
{
if( source.Any() )
{
HashSet
<ObjectId
> lockedLayers
= new HashSet
<ObjectId
>();
/// If includeObjectsOnLockedLayers is true, the entities
/// must be open in the Top transaction, or a Transaction
/// must be passed in (which can be an OpenCloseTransaction).
if( includeObjectsOnLockedLayers && tr == null )
{
DBObject first = source.First();
if( first == null )
throw new ArgumentNullException
( "element" ); if( first.IsTransactionResident && first.Database != null )
tr = first.Database.TransactionManager.TopTransaction;
if( tr == null )
throw new Autodesk
.AutoCAD.Runtime.Exception( ErrorStatus.NoActiveTransactions );
}
foreach( T obj in source )
{
if( obj == null )
throw new ArgumentException
( "element" ); if( !obj.IsWriteEnabled )
{
Entity entity = obj as Entity;
if( entity == null )
{
obj.UpgradeOpen(); // not an Entity
}
else
{
ObjectId layerId = ObjectId.Null;
try
{
layerId = entity.LayerId;
// caller wants to open objects for write on
// locked layers, so use GetObject() on the
// transaction to upgrade the open mode:
if( includeObjectsOnLockedLayers )
{
tr.GetObject( obj.ObjectId,
OpenMode.ForWrite, obj.IsErased, true );
}
else // omit entities on locked layers
{
// if the entity's layer id is in the hashset,
// the layer is locked and we skip the entity,
// avoiding a much more-expensive exception:
if( lockedLayers.Contains( layerId ) )
continue;
// the entity may still be on a locked layer
// that hasn't been added to the HashSet yet
// so we deal with that in the catch block:
obj.UpgradeOpen();
}
}
catch( Autodesk.AutoCAD.Runtime.Exception ex )
{
if( ex.ErrorStatus != ErrorStatus.OnLockedLayer )
throw; // something else is wrong
// add the locked layer's id to the HashSet so
// that we can avoid having to use exceptions
// to deal with additional entities that are
// on this same locked layer:
lockedLayers.Add( layerId );
continue; /// omit this entity from the result
}
}
}
yield return obj;
}
}
}