Author Topic: Best practices for simple lookup functions  (Read 9089 times)

0 Members and 1 Guest are viewing this topic.

jmaeding

  • Bull Frog
  • Posts: 304
  • I'm just here for the Shelties.
Best practices for simple lookup functions
« on: January 12, 2007, 01:18:10 PM »
I am in the process of building my library of subroutines for .net programming with acad.
I have one that retrieves a table record, like a layer or linetype, based on the name or objectID.
I have only done it for the layer name so far, and want to mutate this so it looks up any kind of table object based on the name:

Public Function getLayerObjNet(ByVal name As String) As LayerTableRecord
        Dim db As Database = HostApplicationServices.WorkingDatabase()
        Dim tm As Autodesk.AutoCAD.DatabaseServices.TransactionManager = db.TransactionManager
        Dim layTblRec As LayerTableRecord = Nothing
        Using myT As Transaction = tm.StartTransaction()
            Try
                Dim layTbl As LayerTable = CType(myT.GetObject(db.LayerTableId, OpenMode.ForRead, False), LayerTable)
                If layTbl.Has(name) Then
                    layTblRec = CType(myT.GetObject(layTbl.Item(name), OpenMode.ForRead), LayerTableRecord)
                End If
                myT.Commit()
            Catch
                layTblRec = Nothing
            Finally
                myT.Dispose()
            End Try
        End Using
        Return layTblRec
    End Function

I am wondering these things:
1) To make this work for ObjectID input, I think I can just make another function to set up an overload:
Public Function getLayerObjNet(ByVal oID as ObjectID) As LayerTableRecord
...
Is that a good way to do this? (may be the only way...)

2) Is my error checking ok, any comments welcome.

3) If I want to add a param to make this work for all tables, it would be like this:
Public Function getAnyObjNet(ByVal name As String, ByVal tableType As String) As LayerTableRecord

Now, of course, the return type cannot be LayerTableRecord, it has to switch to the type specified in the tableType param.
Is there an easy way to do this?  Can I somehow tell it to look up the object type from a string name?  Maybe that is what CType can do, I thought that only casts one object type to another though.
Once I know how to look up an object type with a string, I can make much more generic code, that adapts to the object types I am feeding in.
It would be great if there was something like:
GetObjectType("LayerTableRecord")  because I would use a variable for the string of course.  I have a feeling that is super easy...

thanks for any help.




James Maeding

Chuck Gabriel

  • Guest
Re: Best practices for simple lookup functions
« Reply #1 on: January 12, 2007, 01:31:32 PM »
Take a look at the SymbolTable and SymbolTableRecord types for starters.  All of the symbol tables (i.e. BlockTable, LayerTable) inherit from SymbolTable, so you can treat them all generically as SymbolTable objects.  The same relationship exists between SymbolTableRecord and, for instance, BlockTableRecord.

jmaeding

  • Bull Frog
  • Posts: 304
  • I'm just here for the Shelties.
Re: Best practices for simple lookup functions
« Reply #2 on: January 12, 2007, 01:32:56 PM »
One more thing, is it a bad idea to get an object with such a routine, and not do anything to "close" it?
It seems like if I get a reference to an object, I must release that at some point.
Maybe that is only if I open it for write.  I get confused between lisp/VB/ and .net...
James Maeding

jmaeding

  • Bull Frog
  • Posts: 304
  • I'm just here for the Shelties.
Re: Best practices for simple lookup functions
« Reply #3 on: January 12, 2007, 01:39:25 PM »
wow, things get deep fast.  I have read a bunch on inheritance, but not used it much in this way.
Can I use those object types generically, like:
Public Function getLayerObjNet(ByVal name As String) As SymbolTableRecord ?

if I can, is this a form of late binding?
Also, do I need to cast that at some point to the desired type so I can use the props and methods of, say, a layer object?
To me, this casting business is the strangest thing to me compared to VB6.  Its funny how it does not get a lot of press in tutorials.

I hate to seem so ignorant, I don't forget though once I understand it. thanks a bunch
James Maeding

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Best practices for simple lookup functions
« Reply #4 on: January 12, 2007, 01:43:36 PM »
Hi James.

I'm horrible with VB .. it give me a mental block.
I hope you have good fortune with it.

1) Personally, Yes I'd just overload the Method.

2) see initial comment.

3) For now, I'm write purpose built routines.





kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Chuck Gabriel

  • Guest
Re: Best practices for simple lookup functions
« Reply #5 on: January 12, 2007, 01:48:18 PM »
I don't know much about how garbage collection works in .NET, so I can't really answer your first question.

This is a form of late binding, and will have some runtime cost, though I doubt that cost will be significant.

You will need to cast the object to its specific type if you want to access any methods or properties that are not part of the base class interface.

The nice part is you probably can reduce what might otherwise have been a group of functions into a single one (I say probably because I haven't actually tried this in .NET just C++).

jmaeding

  • Bull Frog
  • Posts: 304
  • I'm just here for the Shelties.
Re: Best practices for simple lookup functions
« Reply #6 on: January 12, 2007, 01:51:33 PM »
cool, I'll post what I come up with.
This is all for that modeless dialog tool I wrote about in another post so I'll post the whole project when done.
James Maeding

jmaeding

  • Bull Frog
  • Posts: 304
  • I'm just here for the Shelties.
Re: Best practices for simple lookup functions
« Reply #7 on: January 12, 2007, 02:14:45 PM »
So this is what I have so far for the "generic" table functions:

Public Function getAnyObjNet(ByVal name As String, ByVal tableType As String) As SymbolTableRecord
        Dim db As Database = HostApplicationServices.WorkingDatabase()
        Dim tm As Autodesk.AutoCAD.DatabaseServices.TransactionManager = db.TransactionManager
        Dim symTblRec As SymbolTableRecord = Nothing
        Using myT As Transaction = tm.StartTransaction()
            Try
                Dim symTbl As SymbolTable = CType(myT.GetObject(db.LayerTableId, OpenMode.ForRead, False), tableType) '<---- not correct
                'cannot use tableType as variable for object type, must use explicit name like LayerTable, need ideas here
                If symTbl.Has(name) Then
                    symTblRec = CType(myT.GetObject(symTbl.Item(name), OpenMode.ForRead), SymbolTableRecord) '<---- works but
                    'I would prefer to send back correct cast of object even though function says "As SymbolTableRecord.."
                End If
                myT.Commit()
            Catch
                symTblRec = Nothing
            Finally
                myT.Dispose()
            End Try
        End Using
        Return symTblRec
    End Function

So while I can stay generic in most places, I must specify what kind of table I want to look up at some point.
I am also wondering if I can return a specific object type like LayerTableRecord, even though my function says "as SymbolTableRecord".
thanks
James Maeding

Chuck Gabriel

  • Guest
Re: Best practices for simple lookup functions
« Reply #8 on: January 12, 2007, 02:25:36 PM »
Having taken a closer look at it, I'm not sure this can be done in .NET the way I would do it in C++.

I would use template programming to do something like this in C++ :

Code: [Select]
AcDbSymbolTablePointer<TableType> symbolTable(pDb, AcDb::kForRead);

where TableType is a template parameter to the function that defines the type of symbol table I am interested in opening.

The paradigm in .NET seems to be something like:

Code: [Select]
Database db = HostApplicationServices.WorkingDatabase;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
  LayerTable table = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead);
}

I can't find anything in AutoCAD's managed API that offers the same genericness as the C++ version.

Sorry for the bum steer.

jmaeding

  • Bull Frog
  • Posts: 304
  • I'm just here for the Shelties.
Re: Best practices for simple lookup functions
« Reply #9 on: January 12, 2007, 02:34:45 PM »
ah, a challenge.  Lets see how this develops.  I'll post on the adesk NG's too..
James Maeding

T.Willey

  • Needs a day job
  • Posts: 5251
Re: Best practices for simple lookup functions
« Reply #10 on: January 12, 2007, 03:00:41 PM »
I think Tony T. might have an example of this.  I will see if I can find it, or if I just imaged it out of thin air.
Tim

I don't want to ' end-up ', I want to ' become '. - Me

Please think about donating if this post helped you.

T.Willey

  • Needs a day job
  • Posts: 5251
Re: Best practices for simple lookup functions
« Reply #11 on: January 12, 2007, 03:10:20 PM »
Okay I think this is what I'm talking about < Link >

Here is something else from the same topic by Ton T. < Link >
Tim

I don't want to ' end-up ', I want to ' become '. - Me

Please think about donating if this post helped you.

Chuck Gabriel

  • Guest
Re: Best practices for simple lookup functions
« Reply #12 on: January 12, 2007, 03:17:17 PM »
Okay I think this is what I'm talking about < Link >

Here is something else from the same topic by Ton T. < Link >

Very nice.  Thanks.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8702
  • AKA Daniel
Re: Best practices for simple lookup functions
« Reply #13 on: January 12, 2007, 03:23:51 PM »
OT:
Just a question James, isn’t
Code: [Select]
Using myT As Transaction = tm.StartTransaction()And
Code: [Select]
myT.Dispose()
redundant? myT should be disposed at the end of the using block correct?

edit

I don’t think it hurts anything, I just wondered if the using statement in vb acted the same as in c#
« Last Edit: January 12, 2007, 03:30:20 PM by Danielm103 »

mohnston

  • Bull Frog
  • Posts: 305
  • CAD Programmer
Re: Best practices for simple lookup functions
« Reply #14 on: January 12, 2007, 07:22:24 PM »
Would passing the database along with the layer name be worth while?
Public Function getLayerTableRecordByName(dwgDB as Database, ByVal name As String) As LayerTableRecord
It's amazing what you can do when you don't know what you can't do.
CAD Programming Solutions

Nathan Taylor

  • Guest

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Best practices for simple lookup functions
« Reply #16 on: January 14, 2007, 05:10:41 PM »
Hello Nathan,
Is that a translation of Tony's
Quote
public static ObjectId GetTableRecordId( ObjectId TableId, string Name )
From the Link posted here by Tim ?

You can post code here directly if you like .. :-)
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Nathan Taylor

  • Guest
Re: Best practices for simple lookup functions
« Reply #17 on: January 14, 2007, 05:36:48 PM »
Hello Nathan,
Is that a translation of Tony's
Quote
public static ObjectId GetTableRecordId( ObjectId TableId, string Name )
From the Link posted here by Tim ?

You can post code here directly if you like .. :-)


Hi Kerry, Yes it is.

Code: [Select]
    Public Function GetTableRecordId(ByVal objTableId As ObjectId, ByVal strName As String) As ObjectId
        GetTableRecordId = ObjectId.Null
        Dim objDB As Database = objTableId.Database
        Dim objTrans As Transaction = objDB.TransactionManager.StartTransaction
        Dim objTable As SymbolTable = CType(objTrans.GetObject(objTableId, OpenMode.ForRead, False), SymbolTable)
        If objTable.Has(strName) Then
            Dim objID As ObjectId
            objID = objTable(strName)
            If objID.IsErased Then
                For Each objID In objTable
                    If Not objID.IsErased Then
                        Dim objSTR As SymbolTableRecord = CType(objTrans.GetObject(objID, OpenMode.ForRead, False), SymbolTableRecord)
                        If String.Compare(objSTR.Name, strName, True) = 0 Then
                            GetTableRecordId = objID
                            Exit For
                        End If
                    End If
                Next
            Else
                GetTableRecordId = objID
            End If
        End If
        objTrans.Commit()
        objTrans.Dispose()
    End Function

jmaeding

  • Bull Frog
  • Posts: 304
  • I'm just here for the Shelties.
Re: Best practices for simple lookup functions
« Reply #18 on: January 14, 2007, 07:03:01 PM »
so that would require it to be done in three steps right?
1) get the table ID
2) get the record objectID
3) cast it to the desired specific record type

I am wondering if its such a good idea to split things up so much.  I think that requires three transactions.
These patterns are so new to me, but I bet the arx crowd knows exactly how they like to organize subroutines like this.
That would sure be useful to hear a commentary on how experinced .net or arx'ers like to do things.

One thing I noticed is the code posted uses an ObjectId.Null as the default should the function not find what it was looking for.
Is that any better than using GetTableRecordId = nothing?  Why not return nothing rather than a null ObjectID?

With subroutines, you are always testing for valid returns.  I tend to test for nothing for object returns, and special values for number and string functions.  What do you guys typically do?

James Maeding

Nathan Taylor

  • Guest
Re: Best practices for simple lookup functions
« Reply #19 on: January 14, 2007, 07:51:01 PM »
so that would require it to be done in three steps right?
1) get the table ID
2) get the record objectID
3) cast it to the desired specific record type

I am wondering if its such a good idea to split things up so much.  I think that requires three transactions.
These patterns are so new to me, but I bet the arx crowd knows exactly how they like to organize subroutines like this.
That would sure be useful to hear a commentary on how experinced .net or arx'ers like to do things.

One thing I noticed is the code posted uses an ObjectId.Null as the default should the function not find what it was looking for.
Is that any better than using GetTableRecordId = nothing?  Why not return nothing rather than a null ObjectID?

With subroutines, you are always testing for valid returns.  I tend to test for nothing for object returns, and special values for number and string functions.  What do you guys typically do?

Here is a sample I posted at http://discussion.autodesk.com/thread.jspa?threadID=529533
Code: [Select]
    Private Sub Sample()
        AddRegAppTable("YourApplicationName")
    End Sub

    Private Sub AddRegAppTable(ByVal strAppName As String)
        Dim objDB As Database = HostApplicationServices.WorkingDatabase
        Dim objTrans As Transaction = objDB.TransactionManager.StartTransaction
        Dim objRAT As RegAppTable = CType(objTrans.GetObject(objDB.RegAppTableId, OpenMode.ForWrite, False), RegAppTable)
        Dim objRATR As RegAppTableRecord
        Dim objRATRID As ObjectId = GetTableRecordId(objDB.RegAppTableId, strAppName)
        If objRATRID = ObjectId.Null Then
            objRATR = New RegAppTableRecord
            objRATR.Name = strAppName
            objRAT.Add(objRATR)
            objTrans.AddNewlyCreatedDBObject(objRATR, True)
        End If
        objTrans.Commit()
        objTrans.Dispose()
    End Sub

The example might answer your questions if not I will explain.

Regards - Nathan

jmaeding

  • Bull Frog
  • Posts: 304
  • I'm just here for the Shelties.
Re: Best practices for simple lookup functions
« Reply #20 on: January 14, 2007, 09:41:05 PM »
I get the idea of avoiding passing objects and using id's instead, but the code posted is a sub.  I am wondering about functions that return things.
James Maeding

Nathan Taylor

  • Guest
Re: Best practices for simple lookup functions
« Reply #21 on: January 15, 2007, 05:09:11 PM »
I get the idea of avoiding passing objects and using id's instead, but the code posted is a sub.  I am wondering about functions that return things.
Hi James,

I understand you are trying to simplify things as much as possible. I think returning an ObjectID is the simplest option and would seem to be good paractice given all the AutoCAD methods use this approach. It is probably a bad idea to open an object with a transaction within a function and the return that object. Although if you still wanted to go down that route maybe passing the transaction to the function by reference might be the right approach. I am not overly experienced so take my advice with a grain of salt and I would be happy for others to comment.

I will comment my example to perhaps explain what I think better.

Code: [Select]
    Private Sub Sample()
        AddRegAppTable("YourApplicationName")
    End Sub

'Example adds a Record to the Registered Application Table if it does not exist
    Private Sub AddRegAppTable(ByVal strAppName As String)
        Dim objDB As Database = HostApplicationServices.WorkingDatabase
        Dim objTrans As Transaction = objDB.TransactionManager.StartTransaction

    'Get Registered Application table using existing Transaction
        Dim objRAT As RegAppTable = CType(objTrans.GetObject(objDB.RegAppTableId, OpenMode.ForWrite, False), RegAppTable)
        Dim objRATR As RegAppTableRecord

    'Get ObjectID of Record using posted GetTableRecordId fuction note how simple it is to get ObjectID of desired table using existing Datbase
        Dim objRATRID As ObjectId = GetTableRecordId(objDB.RegAppTableId, strAppName)

    'Test if Record existed note I don't know if there are any benefits using ObjectId.Null over Nothing but since there is a null value of the return type it makes sense to me to use it
        If objRATRID = ObjectId.Null Then

        'Record did not exist so create it
            objRATR = New RegAppTableRecord
            objRATR.Name = strAppName
            objRAT.Add(objRATR)
            objTrans.AddNewlyCreatedDBObject(objRATR, True)
        Else

        'Record exists although we are not using it get it for the example using existing transaction
            objRATR = CType(objTrans.GetObject(objRATRID, OpenMode.ForWrite, False), RegAppTableRecord)
        End If
        objTrans.Commit()
        objTrans.Dispose()
    End Sub

I hope that helps.

Regards - Nathan

jmaeding

  • Bull Frog
  • Posts: 304
  • I'm just here for the Shelties.
Re: Best practices for simple lookup functions
« Reply #22 on: January 15, 2007, 10:24:59 PM »
Interesting, I do like the flow of your code, that does not seem overly chopped up.
I am very interested in your comment on dealing with an object as opposed to an objectID. Somehow, that nags at me like it will come back to bite me (my method, not yours).
I am learning fast how sensitive acad is to an error in a dll.  I was crashing acad on stuff like converting color objects...

Thanks for the reply, we'll get to bullfrog status yet!
James Maeding

jmaeding

  • Bull Frog
  • Posts: 304
  • I'm just here for the Shelties.
Re: Best practices for simple lookup functions
« Reply #23 on: January 17, 2007, 01:47:47 PM »
by the way, got this code from ADN...answers my question on a generic lookup function:

Public Function getAnyObjNet(ByVal name As String, ByVal tableTypeId As ObjectId) As SymbolTableRecord
        Dim db As Database = HostApplicationServices.WorkingDatabase()
        Dim tm As Autodesk.AutoCAD.DatabaseServices.TransactionManager = db.TransactionManager
        Dim symTblRec As SymbolTableRecord = Nothing
        Using myT As Transaction = tm.StartTransaction()
            Try
                Dim symTbl As SymbolTable = myT.GetObject(tableTypeId, OpenMode.ForRead, False)

                If symTbl.Has(name) Then
                    symTblRec = myT.GetObject(symTbl.Item(name), OpenMode.ForRead)
                End If
                myT.Commit()
            Catch
                symTblRec = Nothing
            Finally
                myT.Dispose()
            End Try
        End Using
        Return symTblRec
    End Function

     'Example usage:
     'Define command 'test'
    <CommandMethod("test")> _
    Public Sub Asdkcmd1()
        ' Type your code here
        Dim db As Database = HostApplicationServices.WorkingDatabase
        Dim str As SymbolTableRecord = getAnyObjNet("*Model_Space", db.BlockTableId)

        Dim tm As Autodesk.AutoCAD.DatabaseServices.TransactionManager = db.TransactionManager
        Using myT As Transaction = tm.StartTransaction()
            If str.Name = "*Model_Space" Then
                Dim btr As BlockTableRecord = CType(str, BlockTableRecord)
            End If
        End Using
    End Sub

now that wasn't so hard was it? You just Use an * to tell it the string is meant as an object type.
James Maeding

jmaeding

  • Bull Frog
  • Posts: 304
  • I'm just here for the Shelties.
Re: Best practices for simple lookup functions
« Reply #24 on: January 17, 2007, 02:18:41 PM »
I guess that does not do what I was thinking, it still needs to grab the table outside and cast after.
It does answer a question though.
Notice that an object was returned by the getAnyObjNet function.
Then later, a transaction was opened, and the object cast to the desired type.
It seems it is ok to pass objects around, and do any changes within a transaction.

Note that the code does not commit or dispose of the transaction though, I think it needs to commit.  The Using method will dispose for us, but I think a commit is needed still.
James Maeding

Nathan Taylor

  • Guest
Re: Best practices for simple lookup functions
« Reply #25 on: January 17, 2007, 04:58:02 PM »
I guess that does not do what I was thinking, it still needs to grab the table outside and cast after.
It does answer a question though.
Notice that an object was returned by the getAnyObjNet function.
Then later, a transaction was opened, and the object cast to the desired type.
It seems it is ok to pass objects around, and do any changes within a transaction.

Note that the code does not commit or dispose of the transaction though, I think it needs to commit.  The Using method will dispose for us, but I think a commit is needed still.
The thing I don't like about the code is that the function is determining the OpenMode for the object. So the function is not as generic. Although you could pass the OpenMode as another parameter to the function.

Regards - Nathan

jmaeding

  • Bull Frog
  • Posts: 304
  • I'm just here for the Shelties.
Re: Best practices for simple lookup functions
« Reply #26 on: January 19, 2007, 12:09:34 PM »
oh, good point, I'll change that
James Maeding