Insert new child object?

Insert new child object?

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


shahram posted on Thursday, July 06, 2006

Hi!

Ok,I have a Client object as root, it has a contactcollection(editable list child) and now I want to add a new contact (editable child) so I do like this:(as I understand you can not call save on a child object directly)

MyClient.ContactCollection.BeginEdit();

Contact myContact = MyClient.ContactCollection,AddNew();

//Fill myContact

MyClient.ContactCollection.EndEdit();

MyClient = MyClient.Save();

Now the issue is I use Identity column as my PK in database(it generates and increaments by DB engin)

It means when I create a new Contact Its ContactID is 0 and after that I have inserted it in DB it returns identityID.

But how can I get that identity value because it is internally inserted by Client object(root object)

I mean If I insert a new Client I get back new identity like this:

Client myClient = Client.NewClient();

myClient = myClient.Save() // now myClient has the identity from DB

But in case of child objects how I can do that because it is saved internally by root object.

I hope somebody shows me the way that it is done.

Regards

 

Allann replied on Thursday, July 06, 2006

I am assuming you are using Stored procedures in your database to Select/Insert/Update and Delete your data.

The problem is not in your class but in the sp that saves the data.  It should return the new id as a part of it.  I have included an example, the bit your interested in is the final select statement.  This sets your OUTPUT parameter @id to the value of the ident column.

CREATE PROCEDURE [dbo].[usp_task_insert]

@id int output,

@lastupdate timestamp output,

@name varchar(50),

@description varchar(255)

AS

INSERT INTO [dbo].[TBL_TASK] (

[name],

[description]

) VALUES (

@name,

@description)

SELECT @ID = [TASK_ID], @lastupdate = [lastupdate]

FROM [dbo].[TBL_TASK]

WHERE TASK_ID = SCOPE_IDENTITY()

shahram replied on Thursday, July 06, 2006

Thanks for the reply.

But that is not my problem my SP is correct and returns the identity value created by DB in a output parameter and ...

The issue is that for a root editable object when you do :

Rootobject _object = RootObject.NewRootObject();

_object = _object.Save();

now I have the identity value in _object.Id property but in case it is a child object how I can get back a child object with the identity value:

ChildObject _child = _root.ChildCollection.AddNew();

_child.Name = "nnn";

_root = _root.Save()// now my child object is inserted in DB but here I have no reference to newly inserted childobject with new identity value to bind to win form!

I mean inserting new child object is a common and basic thing so CSLA approch must have a correct way to do this not like now go and find in childcollection and find a child with max Id and return that because it would be simply stupid way and not the elegant OO way that CSLA is about.

I really hope somebody answer to this .

Regards

matt tag replied on Thursday, July 06, 2006

I never store my foreign key values in my child object at all - no need for them.

What I do is pass my root object down to my child object and use the primary key found there as the foreign key. Pseudocode below...
 

So, in the dataportal_update of the root:

<run SQL to save myself>

fChildren.Update(Me, tr)

end sub

then in the Children collection

friend sub Update(p as parent, tr as sqltransaction)

   for each c as child in me.list
       c.Update(p, tr)
   next
end sub

Then, in the child

friend sub Update(p as parent, tr as sqltransaction)

   insert into childtable (pk, fk ....
      values (me.PrimaryKey, p.PrimaryKey...

end sub

Hope this helps.
matt tag

shahram replied on Thursday, July 06, 2006

Thanx for your post but it is not related to my problem as I understand.

When I( as a UI developer) create and insert a new child object how I can find out which of object in childcollection is that newly inserted object.

You have a form, user fylls in and pushes the Insert button on the form, CSLA library inserts the child object, but you need a reference to newly inserted child object to bind it to the form so user can see the identity value returned from DB on the form and countinue with his editing job.

My Csla Library works just find. It is not the issue. My question is how UI developer that uses my CSLA library, can insert a child object and return a reference to newly inserted child object?

 

ajj3085 replied on Thursday, July 06, 2006

You should be able to call the AddNew method of your collection.  That will return a reference to the new object.

matt tag replied on Thursday, July 06, 2006

Ok, how about this - code for AddChild exists in ChildCollection class

public function AddChild as Child
  
   dim r as new Child

   r = Child.NewObject
   Me.List.Add(r)

   return r

end function

UI developer does something like this to add a new object

Sub AddEditChildObject

dim c as Child
dim f as ChildEditForm

fParent.BeginEdit   

c = fParent.Children.AddChild
f = new ChildEditForm
f.ChildToEdit =c   
f.ShowDialog
if f.DialogResult = Ok
   fParent.ApplyEdit
else   'user hit cancel
   fParent.CancelEdit
end if
f.Dispose

End sub

Note that there is no need to worry about primary/foreign keys at this point - we're not doing any work in the database yet.

matt tag

ajj3085 replied on Thursday, July 06, 2006

Matt,

Why add the AddChild method?  All collection objects have AddNew defined as a result of inheriting from BindingList.  All the OP needs to do is override AddNewCore in his collection:

        protected override object AddNewCore() {
            Child result;

            result = Child.NewChild();

            OnAddingNew( new AddingNewEventArgs( result ) );
            Add( result );

            return result;
        }

shahram replied on Friday, July 07, 2006

I am not sure if I understand correctly but after AddNew you get a reference to a new child object that is new so it's PK(a identity column i sql server) is 0 so you must insert it into the database - it means you must call rootobject.Save() and my problem is here after you call rootobject.Save() you get back a reference to rootobject and not a reference to that specefic child object that you have inserted so you can read it's assigned identity id.

I think this is a very basic operation-adding new child object to a database that uses identity columns as PK- so theĀ“re must be a clean and correct OO way to do that.

I am a little bit supprised that such basic thing is so difficult to figure out .

Regards

esteban404 replied on Friday, July 07, 2006

I'm not sure if this is what you're looking for, but I'll give it a stab. I needed to do this and put this into my Update method on the child in the BO:

// either adding or updating
if(this.IsNew)
{
// new, so insert
cm.CommandText = "addRedZoneContact";
cm.Parameters.Add("@RedZoneID", redzone.RedZoneID);
SqlParameter sqlParam = cm.Parameters.Add("@ReturnValue", SqlDbType.Int);
sqlParam.Direction = ParameterDirection.ReturnValue;
}
else
{
// not new, so update
cm.CommandText = "updateRedZoneContact";
cm.Parameters.Add("@RecordID", _RecordID);
}
cm.Parameters.Add("@RoleID", _RoleID);
cm.Parameters.Add("@ContactID", _ContactID);
cm.ExecuteNonQuery();

if
(this.IsNew)
{
RecordID = Convert.ToInt64(cm.Parameters["@ReturnValue"].Value);
}
MarkOld();

In the UI, rebinding or refreshing the form may be necessary to show the new value, but it works.

In this case, the UI is calling for a list to select the Contact and adds it to the collection by ID and ParentID. However, if the contact they want to use is not present, I give them the opportunity to add them by calling up the new contact editor and, in a similar way, get the new id for the contact I just created.

In the case of calling a stored procedure to retrieve an new ID, I did this in an event in the UI:

//
string
myID = (string)ProgramID.GetNewID(MainForm.MyID,"RedZone");
this.RedZone.ProgramID = myID;
DataBind();

The myID value is a new contrivance constructed in the Db., the MainForm.MyID is the ResourceID of the person requesting the new programID (for history).

Those are the two instances I've used.

_E

ajj3085 replied on Friday, July 07, 2006

Is there a reason you need the PK value immediately?  Typically you leave the PK value at its default, allow the user to edit, and when they are doing editing the root object, thats when the child is inserted. 

This may be a clue that you're thinking in data terms instead of business behaviors, as the business objects usually don't care if their children are new or not.  Business objects use composition to define relationships, not numbers.

At any rate, the code I posted will still work.  Calling Child.NewChild would end up calling DataPortal_Create, which could actualy insert a blank row and get the key.  Then you'll have your child with PK already in place.

HTH
Andy

shahram replied on Friday, July 07, 2006

It is not a good idea to first insert a blank line into DB to get back a Identity and then ...

What if user change his mind and click cancel It means you must go to DB and do a delete too!

How the UI developer get access to assigned identity value? This is a simple question and common, it is impossible to do any application and not to need do this but no explanation in book nor in the forum.

shahram replied on Friday, July 07, 2006

I must ask this question from the author that what is the point with this  csla because apparently it doesn't make thing easier nor faster, a lot of code to just make things harder?

I mean why not use a typed dataset and you get all of it generated by Visual studio or a orm and so on.

 

ajj3085 replied on Friday, July 07, 2006

The problem with typed datasets is that as soon as your database changes your applications break as well, and you need to recode.  Nor will a dataset be able to contain business rules (such as if property X has a value, then propery Y must be between A and B). 

Csla does make creating proper business objects eaiser and it also makes building the UI easier too because it enables the use of databinding, which removes most of the plumbing for getting values into and out of your BOs from the controls.

I suggest you read the book, it will explain why you want to create business objects instead of typed datasets, and will also explain how to propely use the framework.  It sounds like you're tying to use Csla as you would Ado.Net (or an ORM) and that is not how its intended to be used.

Andy

shahram replied on Friday, July 07, 2006

Thanx for your reply.

I have actuelly read the book twice and nothing useful about my above problem.

In case you have read the book I would be very glad if you tell me how is the book,s solutuion to above problem.

Regards

ajj3085 replied on Friday, July 07, 2006

Well if you had read the book then you should know 'why not a typed dataset.'

You haven't given any UI code, so its kinda hard to suggest a solution to your problem.  Frankly, I don't see why you even need to keep a reference to the child.  usually children are handled by databinding, so the UI doesn't care about a specific one.

If the UI really does care about a specific one, then its likely the child should really be its own editable root.   The reason you're having a problem is either because you'er not coding your UI correctly (using databinding approrpriately) or your business object design is wrong.

Andy

RockfordLhotka replied on Friday, July 07, 2006

I am having trouble understanding the exact problem, but I think it is this:

  1. You have a root object with a collection of child objects
  2. You add a new child object to the collection
  3. The UI keeps a reference to this new child
  4. You then save the root object (root.Save()) and get back a whole new object graph - including a new root, a new collection and new child objects - and at least that new child object has a new id value assigned by the database
  5. The UI updates its reference to the root object
  6. For some reason you don't want to update the reference to the child object

So far this is covered in the book, and is illustrated by the Resource class in ProjectTracker, with the notable exception of #6.

CSLA doesn't support #6, and can't, because Save() returns a new object graph. You must update all references to use the new object graph.

I agree that this is not ideal, but it is what it is.

If you have many references to your objects scattered throughout your code (probably because you aren't using data binding) then the issue becomes one of notification - somehow the holders of all those references need to know to update themselves to use the new references.

Child objects can't provide this notification, because (on the client side) they aren't responsible for their own lifetimes. But the root object can provide the notification. In your Save() method you can raise an event (which you must declare as discussed in Chapter 3) along this line:

public override Customer Save()
{
  Customer tmp = base.Save();
  OnObjectSaved(this, tmp);
  return tmp;
}

The onObjectSaved() method raises some event (perhaps ObjectSaved), allowing listeners to be aware that "this" has been replaced by "tmp". In your UI layer you can then use an observer pattern or something similar to spread the news that the old object graph is now obsolete.

Copyright (c) Marimer LLC