Hanalei, Hawaii 9/2/2010
438 Posts and Counting

Pass Number 1: ActiveRecordEngine for ASP.NET

Tuesday, November 17, 2009 -

missing-link

I have a SubSonic prototype that’s been sitting on my hard drive for a while – something I’ve been meaning to spruce up for a bit and release. As of right now I’m happy I didn’t – the code would be considered SubSonic’s and I’d rather it get pulled into a larger effort (wink wink, nudge nudge… bat over the head).

I massaged the interfaces I’ve been using and in this post I thought I’d lay it out for comment.

Functional Background

The idea is that you “plug in” the ActiveRecord “stuff” to your web app and use  it as you see fit – with perhaps a default being offered from Phil and team (using one of the ORMs their baking up there… :):):). So imagine that, out of the box, in the Global.asax is a line like this:

Controller.SetActiveRecordEngine(new EFActiveRecordEngine());

Yes yes I know. Settle down.

First Pass

To get all of this to work properly we need to define two interfaces – one that works the database, the other that describes the object. I’ve been sort of thinking a lot about this for a long time… can you tell?

Here’s a starter interface for IActiveRecord, with the goal of keeping it lean and simple:

    public interface IActiveRecord {

        object Add();
        int Update();
        void Save();

        void Delete();
        void Destroy();

        string KeyName();
        string KeyValue();
        string DescriptorName();
        string DescriptorValue();

    }

Aside from the typical CRUD stuff, there are 4 methods that allow you to do some groovy UI work here (Key/Descriptor stuff). With these specified we can hijack the HTMLHelpers to do our bidding – things like DropDowns and simpler tables. Heck we could even do Editor<T> if we wanted.

The next part is the engine – the thing that would do the query stuff:

    public interface IActiveRecordEngine {

        T Get<T>(object key) where T : new();
        T Get<T>(Func<T, bool> expression) where T : new();


        IQueryable<T> All<T>() where T : new();

        T Add<T>(T item) where T : new();
        void AddMany<T>(IEnumerable<T> items) where T : new();

        void Update<T>(T item) where T : new();
        void UpdateMany<T>(IEnumerable<T> items) where T : new();

        int Delete<T>(Func<T, bool> expression) where T : new();
        int Destroy<T>(Func<T, bool> expression) where T : new();

    }

The power of Linq means that we don’t need to define much beyond “All()” – aside from the CRUD stuff. This is our answer to “method missing” with Rails’ ActiveRecord. Well at least sort of.

Putting the Pieces Together

Under the hood the vendor who implements these interfaces would be responsible for allowing the developer to harness their particular ActiveRecordEngine to IActiveRecord – meaning that when object “Post” which implements “LinqToSqlActiveRecord” calls “Add()” then the underlying “LinqToSqlActiveRecordEngine” does the heavy lifting.

Your Thoughts Please – and Your Voice

Data access is a pain in the ass and something that we should really move beyond. If this interests you – heckle some people. Twitter it, blog it. Make yourselves heard. Phil is very much into this (it was our convo that sparked this whole thing) and let’s resolve this API so we can (if we so choose) all speak the same data access language when using ASP.NET.

Talk to me! If you want something like this – you need to speak up.

Related


Gravatar
Damien Guard - Tuesday, November 17, 2009 - The ActiveRecord pattern has the get/save methods as static...

[)amien
Gravatar
Neal Blomfield - Tuesday, November 17, 2009 - All looks good *except* for the first line of code. Having a default AR api is great as it simplifies a lot of those simply CRUDdy web applications and makes it easier for developers to start using these kind of approaches ( especially if they are MS sanctioned ), but tying the AR engine to the controller means you are adding persistence concerns to something that should be focussed entirely on application flow.

If you want to expose the engine via the controller without using any form of DI, a cleaner approach would be something like:

public class ARController : Controller
{
public IActiveRecordEngine ActiveRecord { get; private set; }
public void SetActiveRecordEngine( IActiveRecordEngine engine )
{
ActiveRecord = engine;
}

// ... add a bunch of convenience methods on the controller to
// simplify access to the AR engine ...
}

this means if people want to use this approach can by subclassing the ARController and going from there ( e.g. LinqToSqlARController ), however those that don't are not left having to deal with a bunch of additional methods on the controller that they will never use.

This approach also means those people left in webforms land can use a similar approach - by providing a base ARPage with a similar api.
Gravatar
Marcus McConnell - Tuesday, November 17, 2009 - Looks like a great start. I agree that Rails has an edge for getting a quick and dirty framework running.

Are the KeyName functions designed to get the Primary Key name and Value? I have used Int, Long, GUID, etc. for Primary Keys. Will I need to cast values to implement this interface? Can you give a sample of how it would be used?
Gravatar
Larry Andersen - Tuesday, November 17, 2009 - As long as it's not TOO simple. What you have laid out here is a start, but it's got a long way to go to be useful. For instance, At it's most basic, ordering and paging support should be there. I know you cover this somewhat in SubSonic and it should be applied here. Also, I think it's bad to assume entities will only have a single key field. I know dealing with composite keys complicates things quite a bit, but realistically, and moderate sized app will more than likely have relation tables with composite keys. And finally, I think there needs to be some consideration for lookup values in foreign key tables, especially in web applications where frequently you are dealing with lists upon lists of data and each row you display has to render some friendly name/descriptor from the foreign key table associated with the record. Hope that makes sense.
Gravatar
Benjamin Gram - Tuesday, November 17, 2009 - First off, I love the concepts behind all of this and am very excited to see where it all ends up.

I may be missing something obvious here, but my initial thought is that I wouldn't want my ActiveRecord engine harnessed to my controller. While I agree that controllers (tied with views) manage application flow, I do not always have my persistence buried inside of the controller itself.

It seems to me that I would like to have the ActiveRecord engine be self-contained (or more tightly associated with the model portion of the code-layout) just like ControllerFactory and ViewEngine.

So instead of:

Controller.SetActiveRecordEngine(new EFActiveRecordEngine());

My initialization would be similar to a view engine or controller factory:

ActiveRecordEngine.Engines.Add(new PhoneServicesControllerFactory());
Gravatar
Hernan Garcia - Wednesday, November 18, 2009 - I really like this idea and if we are able to have this backed into MVC somehow, eben an EF implementation :-), that could be fantastic.

I agree with Rob most apps that are written out there are simple, very simple CRUD apps where the use of the Repository, Model, Service, etc. patterns are really no needed.
(Note: Please keep in mind I'm not talking about not applying the SOLID principles here, just over complexity.)

We love Ruby on Rails because we can have an app up and running in no time. We accept the Rails way and we stop worrying about silly things like what framework should I use to do this or that.
Yes, I know you can change the conventions in Rails and when you need your app to grow complexity grows as well.

If we add a pinch of TDD on top of it, refactor our apps when they really need to grow and incorporate more complex patterns should/is trivial.

About multiple field keys, yes I have to work in legacy db's all the time and I found terrible schemas with keys composed of two,three and more fields, but those are not the places where I will use AR, keep your PK simples as well.

We need to keep our tools simple and we need to use the right tool for the right task.
That will make our apps more reliable and easy to maintain.

You cna have all the power tools that you want, and you should use them. But to drive a nail in, a hammer is more than not the right tool to use.

Actually I think of AR as a neumatic nailer, faster than a hammer but not much more complicated and it needs the nails to comply with certain specifications.
Gravatar
Scott - Wednesday, November 18, 2009 - I've been waiting for something like this for a long time. Building most apps should be *simple* - the data access problem should have been solved a long time ago.
Gravatar
Eric Polerecky - Wednesday, November 18, 2009 - Since day one, and still today, ASP.NET MVC has had critics who compare it to rails. Now that the community (or just you) are starting to built out more rails like functionally you get backlash from....I don't know...people that don't like rails.

Keep your head up.

I hope, like the common service locator for IoC, that vendors and oss projects pick up on this.
Gravatar
Troy Goode - Wednesday, November 18, 2009 - Hi Rob, what is the int being returned by the Update method?
Gravatar
alwin - Wednesday, November 18, 2009 - What is the difference between Delete and Destroy? What should be the difference betweeen Save and Update?

public interface IActiveRecord
{
// insert or update into db, sort of like SaveOrUpdate in NH
void Save();

// delete from db
void Delete();

// primary key in db, how to set the id is up to the AR engine
object Id { get; }

// not sure what you would want with these two if you got Id property
//string KeyName();
//string KeyValue();

string DescriptorName();
string DescriptorValue();
}

And then static class ActiveRecord

// in global.asax
ActiveRecord.SetActiveRecordEngine(new EFActiveRecordEngine());

// in the controllers: utility methods to access the methods of the AR engine
ActiveRecord.Get(object id)

Have you looked at Castle's ActiveRecord? It works pretty good, but it's still somewhat complicated. At least it was for me when I just started without ever using RoR before. But now I use it with great pleasure.

How would you handle validation? auditing? unit of work?
Gravatar
Karl - Wednesday, November 18, 2009 - I may be misunderstanding, but it seems wrong to me that the controller would be "harnessed" to the persistence implementation. RoR/Django/Cake have the right idea by making that an implementation detail of the Model.

Get should be static. PHP added late static binding to 5.3 specifically to support that kinda feature. Existing PHP MVC frameworks which don't leverage 5.3 (most of them) have really ugly/stupid syntax for fetching.

The AddMany and UpdateMany seem redundant. You can handle that via overloads or params. Unless I misunderstand the purpose, UpdateMany on an instance seems weird.
Gravatar
Kijana Woodard - Wednesday, November 18, 2009 - I'm starting to see why you're building your own blog. Sorry about the multi-posts.
Gravatar
davethieben - Wednesday, November 18, 2009 - i'm sort of a LINQ n00b, so excuse the question - but if I do:
engine.All().Skip(500).Take(10).ToList()

does the trip to the db return all records, and then the app filters? or does that statement get optimized to only return the 10 records?
Gravatar
Javier Lozano - Wednesday, November 18, 2009 - I can haz codez? :)
Gravatar
Kevin Southworth - Thursday, November 19, 2009 - YES! A common interface for doing data access in ASP.NET would be fantastic! I know MS likes to leave it wide open so that any data access can be used, but I have long wished there was a more opinionated approach to data access in ASP.NET. There should just be "the way". Rails, django, etc. have adopted this approach and even though their bundled "ORMs" might have drawbacks, at least everyone speaks the same language and it's easier to get going with those frameworks because of it.
Gravatar
Ruairi - Thursday, November 19, 2009 - I do have a concern about fat a controller layer. If you allow
IQueryable All() where T : new();

Then all hell can break loose in the controller. Am I missing the point?
Gravatar
Jason - Thursday, November 19, 2009 - "...only to be abated by the mass of confusion that is NHibernate."

Please explain :)
Gravatar
paul - Thursday, November 19, 2009 - Rob, maybe I just haven't had enough kool-aid (or maybe too much...) but I still don' understand what makes the AR style more simple or more useful than having a generic repository. I mentioned it on your other post, too, and I just don't get it. If anything a generic repository is simpler, because my model objects don't have to implement anything if I don't want them to (though an ID property is likely just good sense).

Example: IRepository looks just like your IActiveRecordEngine (or very similar). We're being Opinionated, so we assume that all IDs are ints, or else we make one extra interface that folks can use that are IRepository or similar.

The overall implementation could be set the same way, and controllers could merrily call its methods to do their work.

Entities could be anything, w/o having to implement hte interface you describe, making them simpler and easier to test/debug.

Query encapsulation (e.g. GetCustomerByName("jones")) could be handled quite easily through extension methods.
Gravatar
Anders - Thursday, November 19, 2009 - Have you seen the Active Record from MS demoed at PDC? http://microsoftpdc.com/Sessions/FT18 at about 45 mins in