[Top][All Lists]
[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/
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [nrdo-list] Support for lazily creating dependent objects,
Stuart Ballard <=