Tuesday, November 17, 2009 -

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.
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.
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.
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.
[)amien
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.
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?
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());
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.
Keep your head up.
I hope, like the common service locator for IoC, that vendors and oss projects pick up on this.
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?
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.
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?
IQueryable All() where T : new();
Then all hell can break loose in the controller. Am I missing the point?
Please explain :)
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.