Hit a brick wall with integer primary keys

Hit a brick wall with integer primary keys

Old forum URL: forums.lhotka.net/forums/t/2735.aspx


JonM posted on Thursday, April 19, 2007

In past projects I've used guids as primary keys and things have went well.  The new project uses integers and I've been able to work around them until now.  I just hit a brick wall.  I'm hoping there is a way around, but I don't see it.

Objects:

Editable Root List

      Editable Root

            Editable Child List

                  Editable Child

 

Okay. All of these objects use integers as primary keys (a identity generated integer from the database).  When a new object is created I set the ID to -1.  Then during the save process I return the identity key field from MSSQL with an output paramter and then I've got my real ID!  Here is the problem my root item has a reference to a 'Primary Child Item'.  The only problem is that if the child item is new, my primarychilditemID property will be a -1.  How do I deal with this?  I hope this explains what I'm doing.

ajj3085 replied on Thursday, April 19, 2007

Are you holding a reference to PrimayChildItem?  The reference will still be valid after the save process. 

Yang replied on Thursday, April 19, 2007

CSLA IDs needs to be unique with in the collection. I used this class to generate IDs for  objects not fetched from a data source. The idea is simple, and that is to generate decrementing ID that is unique. I then replace the object ID with the id generated by the DB after insert.

Imports System.Threading

Friend Class IDGenerator

Private Shared ID As Integer = -1

Private Sub New()

End Sub

Public Shared Function GetNextID() As Integer

Return System.Threading.Interlocked.Decrement(ID)

End Function

End Class

 

 

JoeFallon1 replied on Thursday, April 19, 2007

This is how my Codesmith templates generate the Update part of a child BO that needs the parent Integer key for the Insert to the DB. Notice how the ParentBO is passed to the child collection as a Parameter which then passes it to each child.

Protected Friend Overridable Sub Update(ByVal tr As IDbTransaction, ByVal ParentBO As ParentBO )

If Not Me.IsDirty Then
 
Exit Sub
End If

If Me.IsDeleted Then
 
If Not Me.IsNew Then
   
DeleteChildren(tr)
    DAL.ExecuteNonQuery(tr, CommandType.Text, GetSQL.Delete(mChildkey))
    PostDeleteData(tr)
 
End If
 
MarkNew()
Else
 
If Me.IsNew Then
   
InsertData(tr, ParentBO)
    PostInsertData(tr)
 
Else
   
UpdateData(tr)
    PostUpdateData(tr)
 
End If

 
UpdateChildren(tr)
  MarkOld()
End If

End
Sub

Protected Overridable Sub InsertData(ByVal tr As IDbTransaction, ByVal ParentBO As ParentBO)
  DAL.ExecuteNonQuery(tr, CommandType.Text, GetSQL.Insert(ParentBO.Key, etc.
))
 
 
'retrieve the newly generated Identity and update the PK in the BO so the child records will save correctly.
 
mChildKey = CInt(DAL.ExecuteScalar(tr, CommandType.Text, GetSQL.GetIdentity))
End Sub

Joe

mtagliaf replied on Thursday, April 19, 2007

Not the most efficient, but you can do a dual save dealie at the parent level:

SQL insert myself
SQL insert children  <- makes primary child item ID available
SQL update myself with primary child ID

---

matt tag

JonM replied on Friday, April 20, 2007

Matt,

I came to the same conclusion.  I'm trying to get permission to convert over to guids.  The project is still being developed at this point.  Thanks.

RockfordLhotka replied on Friday, April 20, 2007

Another thing to consider is this: the only thing CSLA requires is that GetIdValue() return a unique value for your object.

That value does not have to be a real value!

In other words, you do not have to return your object's real ID value from GetIdValue(). You could return some arbitrary value (int, guid, whatever) that is never stored in the database or used for any other purpose beyond providing a unique ID for your object in memory.

The value of using a real ID value is that the ToString(), GetHashCode() and Equals() overloads provided by BusinessBase then operate on your real ID. But in a great many cases (most?), you don't care if GetHashCode() returns the same value every time your object is loaded from the database. You only care that it is unique at any given instant in time.

ToString() may be a bigger deal, because you probably want it to return something real. So if you do use an arbitrary (non-meaningful) ID value for GetIdValue(), then you'd probably need to override ToString() in each business class to return some meaningful string value.

I sometimes do create a private, unique, non-meaningful ID value just for GetIdValue() - especially if I don't know or care about the real ID value that will ultimately be in the database. Or sometimes I have objects that never do have real unique ID values, or which have very complex compound unique IDs that would be hard or expensive to merge into a value for GetIdValue(). To avoid that complexity, I'll just create an arbitrary int or guid value for every object, new or old, and return it from GetIdValue() - even though that arbitrary value is only used in memory and has no real meaning in the database or anything else.

Stephen Richards replied on Friday, April 20, 2007

More info at Generating temporary unique ID values for objects.

phowatt replied on Friday, June 15, 2007

The technique I use is to create a hash table in the parent object and in the create child method I pass a reference to the parent hash table to the child.  Then when the parent is created the Primary key is returned in the DataPortal_Insert and this value is immediately stored as an entry in the hash table.  The child update is then processed and in the DataPortal_Insert I retrieve the parents primary key value from the refereneced hash table.  I use an enum to provide named keys for the hash table and I use the vaules from the enum to set and retrieve the entries in the hash table.

Copyright (c) Marimer LLC