nrdo-list
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nrdo-list] Support for lazily creating dependent objects


From: Stuart Ballard
Subject: [nrdo-list] Support for lazily creating dependent objects
Date: Wed, 26 May 2004 09:28:03 -0400
User-agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.5) Gecko/20031107 Debian/1.5-3

I've just checked in support in nrdo's C# binding for creating dependent objects lazily. There are a few places inside cmScribe (NetReach's CMS) where this would have been very handy, and I plan on using it a lot inside the new layout branch. Since it might be useful to others (both inside NetReach and out) I'll describe the feature briefly (okay, not so briefly, when am I ever brief?) here.

I'm using ASP.NET as an example but the same problem crops up in other uses of nrdo too.

The normal way to use nrdo in an ASP.NET manager page is something like this (assuming Foo is a nrdo-generated table):

private Foo foo;

void Page_Load(...) {
  foo = Foo.GetById(Nint.Parse(Request.QueryString["fooId"]));
  if (foo == null) foo = new Foo(param1, param2);
  if (!IsPostBack) {
    SomeTextBox.Text = foo.SomeField;
    SomeCheckBox.Checked = foo.SomeOtherField;
  }
}

void Update_Click(...) {
  if (!Page.IsValid) return;
  foo.SomeField = SomeTextBox.Text;
  foo.SomeOtherField = SomeCheckBox.Checked;
  foo.Update();
  Response.Redirect(somewhere);
}

(if you weren't using nrdo objects in this way, it's worth understanding this technique to see if it could simplify your code)

This allows "foo" to be used 'as if' it already exists throughout the code, even though it actually doesn't; it also means that the Update_Click method doesn't have to care whether it's updating an existing record or adding a new one - all that was handled by the one-liner "if (foo == null) ..." in Page_Load.

An important point to notice is that the record doesn't actually get created until the foo.Update() in Update_Click, which means that if the user hits Cancel instead, the database is never touched.

This works well if all your data is in one table, but sometimes it's not. Sometimes you need to create entries in two separate tables when the update button is clicked, and usually in that case one of them has a foreign key to the other. And often, the foreign key field isn't writable - it needs to be supplied to the constructor. The handler for the foo == null case in Page_Load ends up looking like this:

if (foo == null) {
  bar = new Bar(param1);
  bar.Update();
  foo = new Foo(param2, bar.Id);
}

The problem is that the bar.Update() line violates the principle that the database doesn't get touched until Update_Click. If the user hits cancel, the Bar record is still created. In fact a new Bar record for every visit to the page, including postbacks (unless you're clever and keep the BarId around in ViewState). You can delete the record in Cancel_Click, but you can't handle the case where the user just closes their browser window. The bar.Update() line can't just be omitted, though, because otherwise you'll get an error when you try to access bar.Id in the next line (because the Id field is generated by the database on first insert).

The new code I've checked in solves this problem by allowing you to write code like this instead:

if (foo == null) {
  bar = new Bar(param1);
  foo = new Foo(param2, bar);
}

Instead of trying to set the BarId field of foo immediately, this saves the bar object itself. When you call foo.Update(), it first calls bar.Update() and internally grabs its Id before updating its own record.

Specifically, this technique is available if you have a readonly field that is a foreign key to a 'pkey sequenced' (identity) field on another table. If you have more than one field that meets that criteria, you can do both at once:

if (foo == null) {
  bar = new Bar(param1);
  baz = new Baz(param2);
  foo = new Foo(param3, bar, baz);
}

One thing to watch out for: in an ideal world trying to get the value of foo.BarId before foo has been updated would return an error (just like getting foo.Id would), but in fact it currently just gives you zero. This is likely to get fixed eventually. It's also likely that eventually I'll add a similar trick for non-readonly fields (so that, say, if BazId was a non-readonly foreign key, you could do foo.Baz = new Baz() and have it lazily fill in the BazId as necessary.

I know I'm going to find this very useful; it will fix a few long-standing problems with records getting created and never cleaned up. Maybe others will find it useful too.

Enjoy,
Stuart.

--
Stuart Ballard, Senior Web Developer
NetReach, Inc.
(215) 283-2300, ext. 126
http://www.netreach.com/





reply via email to

[Prev in Thread] Current Thread [Next in Thread]