LINQ and the AutoCAD .NET API (Part 6)

Write operations

This is the sixth in a series of posts on LINQ an the AutoCAD .NET API. Here's a complete list of posts in this series.

Introduction

This is part 6 of our series on LINQ and the AutoCAD .NET API. Currently the GeneralHelper class only support read operations. If we perform a write operation on one of the objects we pulled out of the database, we get an exception. In this blog post we want to correct this behaviour.

UpgradeOpen()

On the level of a single database object, the DBObject class provides two methods that let us change the open mode of a DBObject: UpgradeOpen() makes a DBObject writable, DowngradeOpen() makes it read only. Since the for-read-mode is currently our default mode, we first look at how to find a procedure to open a collection of DBObjects for for-write.

Collections

When we use the GeneralHelper class, which collections are we dealing with? Well, the access properties of the GeneralHelper class are of type IEnumerable<T>, where T is derived from DBObject. Since every .NET collection implements IEnumerable<T>, we could define an extension method for IEnumerable<T> and restrict T to DBObject. In other words: whenever a .NET container class contains items that are derived from DBOBject, our extension method to make items for-write would become available. Let's look at the implementation:


public static class IEnumerableExtensions
{
  public static IEnumerable<T> ForWrite<T>(this IEnumerable<T> source)
    where T : DBObject
  {
    foreach (var item in source)
    {
      if (!item.IsWriteEnabled)
      {
        item.UpgradeOpen();
      }
  
      yield return item;
    }
  }

  public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
  {
    foreach (var item in source)
    {
      action(item);
    }
  }
}

Listing 1: IEnumerableExtensions

The implementation of ForWrite is straightforward: we iterate the source items, which are DBObjects due to the type constraint, and upgrade each item, in case it is not yet write-enabled. The ForEach method is just for convenience, to easily iterate elements of an enumerable of DBObjects.

Now as an example, to delete all lines and circles from the model space, we now can do the following:


[CommandMethod("DeleteLinesAndCircles")]
public static void DeleteLinesAndCircles()
{
  using (var helper = new GeneralHelper())
  {
    foreach (var layer in helper.ModelSpace
                                .OfType<Line>()
                                .ForWrite())
    {
      layer.Erase();
    }
    
    var circles = new List<Circle>();
    
    circles.AddRange(helper.ModelSpace
                           .OfType<Circle>());
                           
    foreach (var circle in circles.ForWrite())
    {
      circle.Erase();
    }
  }
}

Listing 2: DeleteLinesAndCircles

As we can see, the ForWrite method is not tied to any of our GeneralHelper methods or properties, but also works perfectly fine with a list of Circles (line 20).

For the sake of completeness, we can add a ForRead extension method as well:


public static IEnumerable<T> ForRead<T>(this IEnumerable<T> source)
  where T : DBObject
{
  foreach (var item in source)
  {
    if (item.IsWriteEnabled)
    {
      item.DowngradeOpen();
    }

    yield return item;
  }
}

Listing 3: ForRead extension method


Commit()

If we run the example in listing 2, we don't get any error, because the objects are in the correct state: we want to make changes to the objects and their state is changed to for-write via the call to ForWrite(). But in the transaction based world of AutoCAD objects, opening objects for-write is only the first step. To make any changes persistend, we finally have to commit the transaction.

So for starters, it would be OK to commit the transaction at the latest possible moment, that is, just right before the transaction is disposed. So we simply add the commit to the Dispose method of the GeneralHelper class:


public void Dispose()
{
  if (tr != null && !tr.IsDisposed)
  {
    tr.Commit();
    tr.Dispose();
  }
}

Listing 4: Dispose method

With this change, the example in listing 2 works perfectly fine and the changes are persisted in the database.


What's next

By now we've come pretty far. The GeneralHelper class now supports read and write operations in a convenient way. In the next post we will have a look at how to improve the creation of objects and we will do some refactoring. Stay tuned.