eAtMaxReaders on Database.Insert - objects not being closed?

eAtMaxReaders on Database.Insert - objects not being closed?

Anonymous
Not applicable
1,489 Views
5 Replies
Message 1 of 6

eAtMaxReaders on Database.Insert - objects not being closed?

Anonymous
Not applicable

I am automating an operation in which lots of block-DWG's (created using WBLOCK) is inserted into another drawing. I read the block-DWG and insert it into the BlockTable:

using (Database sourceDb = new Database(false, false))
{
    sourceDb.ReadDwgFile(sourceDwg, FileOpenMode.OpenForReadAndAllShare, true, "");
    return targetDb.Insert(blockName, sourceDb, false);
}

 

Seemingly at random, targetDb.Insert throws an eAtMaxReaders exception. I have figured out that I am probably opening an object more than 255 times, but which object can it possibly be that is being opened from Database.Insert?

 

All around my code I am heavily relying on AutoCADCodePack, which wraps read-only operations into this neat little method:

 

 

public static DBObject QOpenForRead(this ObjectId dboId, Database db)
{
    using (Transaction trans = db.TransactionManager.StartOpenCloseTransaction())
    {
        return trans.GetObject(dboId, OpenMode.ForRead);
    }
}

 

I use this everywhere like this:

var dbObj = objId.QOpenForWrite<DBObject>();

// various read operations on the DBObject

 Can the issue here be that the object is not being properly closed  using this method? I am not getting the hang of how this can even work; wouldn't all opened objects within a transaction get closed and disposed by the using statement before returning the object?

0 Likes
1,490 Views
5 Replies
Replies (5)
Message 2 of 6

Alexander.Rivilis
Mentor
Mentor

1. You select wrong forum. Right forum is  https://forums.autodesk.com/t5/net/bd-p/152

2. It looks like you forget to close an opened object. It is possible to open the object without closing only 255 times.

Відповідь корисна? Клікніть на "ВПОДОБАЙКУ" цім повідомленням! | Do you find the posts helpful? "LIKE" these posts!
Находите сообщения полезными? Поставьте "НРАВИТСЯ" этим сообщениям!
На ваше запитання відповіли? Натисніть кнопку "ПРИЙНЯТИ РІШЕННЯ" | Have your question been answered successfully? Click "ACCEPT SOLUTION" button.
На ваш вопрос успешно ответили? Нажмите кнопку "УТВЕРДИТЬ РЕШЕНИЕ"


Alexander Rivilis / Александр Ривилис / Олександр Рівіліс
Programmer & Teacher & Helper / Программист - Учитель - Помощник / Програміст - вчитель - помічник
Facebook | Twitter | LinkedIn
Expert Elite Member

0 Likes
Message 3 of 6

Anonymous
Not applicable
  1. Oops, sorry!

  2. Yes, but I read that the Transaction will handle the closing for me, correct? I need help understanding what is actually happening here. When I return an opened DBObject within a transaction, will the returned object be kept open? I assume this is the case, since it actually works. But why doesn't the using statement close the DBObject before it gets returned?
0 Likes
Message 4 of 6

ActivistInvestor
Mentor
Mentor

The method you show below (QOpenForRead) returns a DBObject after aborting the transaction that it was opened with. The returned DBObject is closed when the transaction is aborted, so it's returning an already-closed DBObject. The fact that a closed DBObject may still be usable is not something you should rely on. Whatever 'code pack' you got that from you should probably not rely too heavily on.

 

While it's possible that the third argument to Insert() (preserveSourceDatabase) could have something to do with it, I suspect the issue may be somewhere else, in code you didn't include in your post. Try passing true for the third argument to Insert().


@Anonymous wrote:

I am automating an operation in which lots of block-DWG's (created using WBLOCK) is inserted into another drawing. I read the block-DWG and insert it into the BlockTable:

using (Database sourceDb = new Database(false, false))
{
    sourceDb.ReadDwgFile(sourceDwg, FileOpenMode.OpenForReadAndAllShare, true, "");
    return targetDb.Insert(blockName, sourceDb, false);
}

 

Seemingly at random, targetDb.Insert throws an eAtMaxReaders exception. I have figured out that I am probably opening an object more than 255 times, but which object can it possibly be that is being opened from Database.Insert?

 

All around my code I am heavily relying on AutoCADCodePack, which wraps read-only operations into this neat little method:

 

 

public static DBObject QOpenForRead(this ObjectId dboId, Database db)
{
    using (Transaction trans = db.TransactionManager.StartOpenCloseTransaction())
    {
        return trans.GetObject(dboId, OpenMode.ForRead);
    }
}

 

I use this everywhere like this:

var dbObj = objId.QOpenForWrite<DBObject>();

// various read operations on the DBObject

 Can the issue here be that the object is not being properly closed  using this method? I am not getting the hang of how this can even work; wouldn't all opened objects within a transaction get closed and disposed by the using statement before returning the object?


 

Message 5 of 6

Anonymous
Not applicable

I changed the QOpenForRead to take an action parameter instead:

 

        public static void QOpenForRead<T>(this ObjectId dboId, Action<T> action) where T : DBObject // newly 20130122
        {
            using (Transaction trans = dboId.Database.TransactionManager.StartOpenCloseTransaction())
            {
                action(trans.GetObject(dboId, OpenMode.ForRead) as T);
            }
        }

I changed my function calls accordingly, so now they all only read open objects. But the problem persists. I also tried setting the third parameter in the Database.Insert call to true, but no luck.

Instead, I found another candidate. I copy properties from one block to another using these methods; for reading:

        public static object GetBlockProperty(this ObjectId block, string name)
        {
            object value = null;
            block.QOpenForWrite<BlockReference>(br =>
            {
                DynamicBlockReferencePropertyCollection props = br.DynamicBlockReferencePropertyCollection;

                foreach (DynamicBlockReferenceProperty prop in props)
                    if (prop.PropertyName == name) {
                        value = prop.Value;
                        break;
                    }

            });

            return value;
        }

and exactly the same procedure for setting:

        public static void SetBlockProperty(this ObjectId block, string name, object value)
        {
            block.QOpenForWrite<BlockReference>(br =>
            {
                DynamicBlockReferencePropertyCollection props = br.DynamicBlockReferencePropertyCollection;

                foreach (DynamicBlockReferenceProperty prop in props)
                    if (prop.PropertyName == name)
                    {
                        Debug.WriteLine("Setting block parameter " + name + "=" + value.ToString());
                        prop.Value = value;
                        break;
                    }

            });
        }


QOpenForWrite works just like QOpenForRead:

        public static void QOpenForWrite<T>(this ObjectId dboId, Action<T> action) where T : DBObject
        {
            using (Transaction trans = dboId.Database.TransactionManager.StartTransaction())
            {
                action(trans.GetObject(dboId, OpenMode.ForWrite) as T);
                trans.Commit();
            }
        }



I copy one parameter value from one block to another like this:

var value = block1.GetBlockProperty("parameter");

block2.SetBlockProperty("parameter", value);

If DynamicBlockRefrenceProperty.Value returns a direct reference to the Value, we will end up with two blocks with parameters pointing to the same value. I can see how this can cause problems. But how would I proceed to copy the value instead? The Value is an object, which cannot be cloned. Any neat trick for this?

0 Likes
Message 6 of 6

ActivistInvestor
Mentor
Mentor

@Anonymous wrote:



I copy one parameter value from one block to another like this:

var value = block1.GetBlockProperty("parameter");

block2.SetBlockProperty("parameter", value);

If DynamicBlockRefrenceProperty.Value returns a direct reference to the Value, we will end up with two blocks with parameters pointing to the same value. I can see how this can cause problems. But how would I proceed to copy the value instead? The Value is an object, which cannot be cloned. Any neat trick for this?


The value you pass when you set a dynamic block property's Value property is copied internally. AutoCAD doesn't store references to managed objects in its database, so that isn't an issue.

0 Likes