Collections

Collections

stefanveurink68AXD
Advocate Advocate
1,110 Views
9 Replies
Message 1 of 10

Collections

stefanveurink68AXD
Advocate
Advocate

Guys, I've had the same 'problem' now couple of times, wonder what's your thoughts on this: 

 

for example, I've turned this code into an method, with as input an hatch, and as output the hatches boundaries. something like: 

 

public static DBObjectCollection hatchbounds(Hatch hatch)
{
   DBObjectCollection resultaat = new DBObjectCollection();
   Document doc = Application.DocumentManager.MdiActiveDocument;
   Editor ed = doc.Editor;
   using (Transaction tr = doc.TransactionManager.StartTransaction())

   if (hatch != null)
      {
      BlockTableRecord btr = tr.GetObject(hatch.OwnerId, OpenMode.ForWrite) as BlockTableRecord;
      Plane plane = hatch.GetPlane();
      int nLoops = hatch.NumberOfLoops;
      for (int i = 0; i < nLoops; i++)
         {
         HatchLoop loop = hatch.GetLoopAt(i);
         if (loop.IsPolyline)
            {
            using (Polyline poly = new Polyline())
               {
               int iVertex = 0;
               foreach (BulgeVertex bv in loop.Polyline)
                  {
                  poly.AddVertexAt(iVertex++, bv.Vertex, bv.Bulge, 0.0, 0.0);
                  }
               resultaat.Add(poly);
               //tr.AddNewlyCreatedDBObject(poly, true);
               }
            }
         }
      }
}

 

In the main code I want to decide whether the boundaries should be drawn or not. So I've got (something like) this: 

 

DBObjectCollection bds = hatchbounds(tijd);

//DECIDE WHETHER THEY SHOULD BE DRAWN OR NOT

foreach (Entity a in bds)
   {
   a.setpropertiesfrom(tijd)
   blort.AppendEntity(a);
   trat.AddNewlyCreatedDBObject(a, true); 
   }

 

Now this code is not working. it gives a "eNullEntityPointer"-error or in some cases an fatal error in autocad.  Strange, because when putting them in the same code (so without a method) they should work right?

 

However, what i'm mostly interrested in is not how to solve this, but:

 

Since working with an dbobjectcollection, is it correct I need to add the objects to the database in the first method? Because, maybe from the main method, it seems unnecessary to do this right? Why would I add them to the database and erase them in the main code? Is that the only way? And there isn't something like just an objectCollection. or EntityCollection. I've also tried making a Curve2dCollection, but converting the curves to Entities seems lots of extra work. Next to that, the most important reason for asking this question is I trying to understand exact working of the database, not to get the code working. 

 

Anyhow, it isn't all perfectly clear to me, any explantion or what is going wrong would be appreciated. 

0 Likes
Accepted solutions (2)
1,111 Views
9 Replies
Replies (9)
Message 2 of 10

stefanveurink68AXD
Advocate
Advocate

For example, maybe this makes the question more concrete: 

 

at the line 

resultaat.Add(poly)

 

When i add a watch to the "poly", everything seems perfectly fine. 

 

However, when watching it in "resultaat", it looks totally screwed, all information is lost. 

 

Why is this. What is going wrong. I really need to understand the different hierarchies cause this is going nowhere.... 

 

And yes I have read the developpers guide. 

 

 

0 Likes
Message 3 of 10

_gile
Consultant
Consultant

Hi,

 

Just two things:

1. assuming the method have a Hatch instance as parameter, this Hatch may belongs to an active transaction. So you should use this active transaction instead of starting a new one.

2. You have to explicitly dispose the DBObjects contained in the collection if you do not add them to a databse and a transaction.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 4 of 10

norman.yuan
Mentor
Mentor
Accepted solution

My comments/critiques to the code:

 

Some minor issues, which may or may not be problematic, depending on the context the hatchbounds() method is intended to be used:

 

1. It would be better to use ObjectId of the Hatch entity as argument, instead of Hatch entity, unless your intention is to ALWAYS use this method inside an open Transaction by calling code. 

 

2. Now that you use Hatch entity as argument, meaning the method is called WITHIN a open Transaction, it is better to use the same Transaction inside this methods, rather than open yet another transaction: you can either take the outside Transaction as another argument, or get TopTransaction from TransactionManager.

 

3. Again, now that the method is used WITHIN an Transaction opened by calling code, using MdiDocument to open another transaction would limit the code's usability: what if you want to do the same thing with an side database, or an open document that is not active document,? Obviously, your code cannot be reused in this case.

 

To make the code more reusable, it would be better to define it this way:

 

public static DBObjectCollection hatchbounds(ObjectId hatchId)

{

    ...   

    using (var tr = hatchId.Database.TransactionManager.StartTransaction())

    {

         ....

     }

    ....

}

This way, this method can be used against either opened drawing, be it active or not, or a side database.

 

Now about the error "eNullEntityPointer":

 

The problem is not about DBObjectCollection the method returns, which should contain newly created, non-database-residing polylines. Because the DBObjects in the collection is not database-residing, you can pass them around, but to remember to either add them into database, or DISPOSE explicitly them by your code. The reason you got that error is you actually DISPOSED polylines your code created before the method hatchbounds() returns, that is, the DBObjectCollection returned by the method contains disposed entity pointers. The offensive code is:

 

            using (Polyline poly = new Polyline())
               {
               int iVertex = 0;
               foreach (BulgeVertex bv in loop.Polyline)
                  {
                  poly.AddVertexAt(iVertex++, bv.Vertex, bv.Bulge, 0.0, 0.0);
                  }
               resultaat.Add(poly);
               //tr.AddNewlyCreatedDBObject(poly, true);
               }
            }

 

That is, since you wrapped newly created Polyline with "using (...){...}" block, the Polyline is disposed when the using... block ends! You should remove the "using... " block here, and use "using..." block for each DBObject in the collection in the calling code where the DBObjectCollection is used/iterated.

 

 

Norman Yuan

Drive CAD With Code

EESignature

Message 5 of 10

stefanveurink68AXD
Advocate
Advocate

This last 3 senteces of Norman Yuan sound very convincing, thanks, I'll check this out (would have checked out before posting this but allready answered to Gile before I saw second answer). 

 

BTW I never really understood difference between: 

(using tr = doc.Database.TransactionManager.StartTransaction());

and just: 

Transaction tr = doc.Database.TransactionManager.StartTransaction();

but that's another topic:-)

 

0 Likes
Message 6 of 10

_gile
Consultant
Consultant

@stefanveurink68AXD  a écrit :

BTW I never really understood difference between: 

(using tr = doc.Database.TransactionManager.StartTransaction());

and just: 

Transaction tr = doc.Database.TransactionManager.StartTransaction();

but that's another topic:-)


As shown by @norman.yuan this is precisely the topic.

 

A using statement is a convinient way to insure diposing objects that implement IDisposable whatever happens in the using block of code.

In other words:

using (var tr = db.TransactionManager.StartTransaction())
{
    // do some stuff with the transaction here
}

is equivalent to:

var tr = db.TransactionManager.StartTransaction();
try
{
    // do some stuff with the transaction here
}
finally
{
    tr.Dispose();
}

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 7 of 10

stefanveurink68AXD
Advocate
Advocate

Okay, at first thanks, the error about using polyline... very relevant, should have seen this, works fine now. Second, I tried different combinations of passing the object, objectid, transaction, en document, at first it as giving all kinds of strange errors like "e not open for write" or "cant append because state of object" but after restarting visual studio and doing it again I cant reproduce these errors, so guess the problem was in there. 

 

Only way I can't get my code to work correctly yet is with splines. This gives a fatal error at the 

blort.AppendEntity(a);

in the main code. so can't find out whats the problem either, because there's not any errors given by visual studio. But okay, can live with that for now, maybe going to research this later. 

 

Third: thanks at Norman Yuan for explaining about whether to use or pass through the transaction and document. I understand what your saying. Doesn't seem to make a real difference for what I'm trying to achieve at this moment, but I'm very interrested in any thoughts on my code, so I can understand it better myself. 

 

Only part I don't understand is whats the difference between passing the ObjectID instead of the Object itself. What is the difference? Or is the important the difference the fact that by passing the ObjectID you can use other transactions and documents, and when passing the Object you can't? Don't see how this makes sense then, but maybe it's because I haven't dealt with this yet. Is it for the case in which you would only like to READ the object and not Edit it? 

 

-----

 

At least, is still don't see the right hierachy/structure. There's a drawing. this drawing can be seen as a database. On this database you can do a transaction. In this transaction you can change a blocktable(record). In this blocktable(record) you can play with entities. But where's the object in this structure? Is an object 1 special case of an entity? like dimstyle is another special case of an entity? But why is it called DBobject then? since that makes me think it is allready in the database? Like when finding the intersectionpoint between to entities, do those entities have to be in the database allready? Or can I find the intersectionpoint between a line in the blocktable or database (so after put it through a transaction), and a not yet appended line in a blocktable(record)? 

 

And like you have curve2D and a curve2Dcollection. you have curve3D and curve3Dcollection. But to make a collection of just Curves, you should do this in a DBcollection? And a Curve is not an Entity, but it IS an object? how does this make sense? Because this instead makes me think objects is not part of entities? And why is it so hard to change a curve into an entity then?

 

And why make a difference between and object and an objectid? the id points to the objects location in the database I guess? So the object itself uses more memory then just the ObjectID? Like a pointer in c++?

 

Please correct me in these last 3 paragraphs. Would be very appreciated. I've written quiet some .net code allready but most of the time I don't really understand what I'm doing, its more by trial and error.  I really feel like I'm missing the point somewhere. Any correction could be significant in this 'wrong mindset'. 

 

@Anonymous thanks for the explanation, started to realise this myself when changing the code, guess it is clear to me now, i see it's purpose 🙂

0 Likes
Message 8 of 10

_gile
Consultant
Consultant

All opened or newly created DBObjects have to be disposed. Transactions provivide a convinient way to dispose DBObject which are opened with the transaction or added to it as soon as the transaction is disposed. That means they cannot be used outside the transaction scope.

public static void Test()
{
    var doc = AcAp.DocumentManager.MdiActiveDocument;
    var db = doc.Database;
    using (var tr = db.TransactionManager.StartTransaction())
    {
        var circle = new Circle(Point3d.Origin, Vector3d.XAxis, 10.0);
        var curSpace = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
        curSpace.AppendEntity(circle);
        tr.AddNewlyCreatedDBObject(circle, true);
        tr.Commit();
    } // <- 'tr', 'curSpace' and 'circle' are all disposed here
}

If you want to pass a DBObject as argument to a method, you should also pass the active transaction.

public static void Test()
{
    var doc = AcAp.DocumentManager.MdiActiveDocument;
    var db = doc.Database;
    using (var tr = db.TransactionManager.StartTransaction())
    {
        var circle = new Circle(Point3d.Origin, Vector3d.XAxis, 10.0);
        AddToCurrentSpace(circle, tr);
        tr.Commit();
    } // <- 'tr', 'curSpace' and 'circle' are all disposed here
}

private static void AddToCurrentSpace(Entity entity, Transaction tr)
{
    var curSpace = (BlockTableRecord)tr.GetObject(entity.Database.CurrentSpaceId, OpenMode.ForWrite);
    curSpace.AppendEntity(entity);
    tr.AddNewlyCreatedDBObject(entity, true);
}

or get it from the DBObject argument:

public static void Test()
{
    var doc = AcAp.DocumentManager.MdiActiveDocument;
    var db = doc.Database;
    using (var tr = db.TransactionManager.StartTransaction())
    {
        var circle = new Circle(Point3d.Origin, Vector3d.XAxis, 10.0);
        AddToCurrentSpace(circle);
        tr.Commit();
    } // <- 'tr', 'curSpace' and 'circle' are all disposed here
}

private static void AddToCurrentSpace(Entity entity)
{
    var db = entity.Database;
    var tr = db.TransactionManager.TopTransaction;
    var curSpace = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
    curSpace.AppendEntity(entity);
    tr.AddNewlyCreatedDBObject(entity, true);
}

 

If you need to access to a DBObject from outside the scope of the transaction used to open it or added to, you have to use the ObjectId of the DBObject which is an identifier (based on Int64) and can be accessed even when the DBOject have been disposed. In this case you'll have to Open the ObjectId, typically using a transaction.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 9 of 10

stefanveurink68AXD
Advocate
Advocate

Right, thanks for the explanation, guess thinks are more clear to me now. Just two things: 

 

- if I understand it right, this means an DBobject is not an object in the database?

 

- If I create an DBobject within a transaction, add it to an DBobjectcollection created outside the transaction, the DBobject still is in the collection after disposing it right? So it, in some sense, doesn't ADD the DBObject to the collection, but more COPIES into it. Or, in an other way, it doesn't add the DBObject itself, but instead the ObjectID. 

 

Still don't really see the difference between object and entity by the way. 

 

0 Likes
Message 10 of 10

_gile
Consultant
Consultant
Accepted solution

My English is not fluent enough to clearly explain all this. Please, attentively read this section of the docs and the related topics.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub