News, examples, tips, ideas and plans.
Thoughts around ORM, .NET and SQL databases.

Saturday, August 01, 2009

DataContext vs Query.All

There is an interesting architectural question: why we don't use well-known DataContext, as well as do not provide any replacement for it?

1. DataContext brings serious disadvantages for extensibility / component-based frameworks

DO4 is designed for extensibility. We imply the whole application consists of a set of relatively loosely coupled components (modules, assemblies) containing their own persistent types.

Imagine you build a security module, and you want to run a query returning all the Users that belong to a particular Role role. DO4 allow you to write it as (Type{T1, T2} is a generic type here, since Blogger eats my <...> on post updates):

from u in Query{Users}.All
where u.Roles.Contains(role)
select u;

Now let's imagine how this might look with DataContext:

from u in dataContext.Users
where u.Roles.Contains(role)
select u;

Seems good as well, yes? But there are two problems:
1) Your security module knows nothing about an application it is used in, and thus - about a particular type of its own DataContext.
2) You must pass a particular instance of DataContext (~= pass a Session) to the place where this query is executed. Well, let's agree this issue is easy to solve - we simply pass it directly to any place where queries might run.

Ok, let's try to solve the problem. Imagine we declared ISecurityDataContext in our security module returning Users (IQueryable{User}) & Roles, and implemented this interface in application's DataContext.

So now our query might look as:

from u in securityDataContext.Users
where u.Roles.Contains(role)
select u;

It looks like it must work, yes? It will work for exactly this case. But it won't work for a bit more complex one, like this:

from u in securityDataContext.Users
where u.Roles.Contains(securityDataContext.Roles.Single(role))
select u;

What's changed? Well, I just referred to our securityDataContext inside expression in Where clause. It won't work, because almost any LINQ translator recognizes only the IQueryable expressions it "knows", so here it will stuck on securityDataContext.Roles method call. From the point of translator, it is unknown Roles method of unknown ISecurityDataContext type!

AFAIK, none except DO4 can handle this now. But we do: see issue #333. Btw, we implemented this mainly to be fully compatible with DataContext (repository) pattern, although our own approach is more generic.

Summary: developing of loosely-coupled comonents becomes much more complex in case you have DataContext. Of course you can workarkound all the mentioned issues, but it will be painful. In our case you must simply do what you want.

2. Particular DataContext is bound to a particular session

Thus you can't e.g. put an IQueryable you want to execute/modify further into a static variable, since it is bound to the original DataContext instance. Its subsequent execution will eventually fail.

And, as it was mention, you must care about passing the DataContext instance everywhere where queries might be executed.

Ok, how DO4 resolves these issues?

1. It provides Query{T}.All static member instead of DataContext.SomeType members.

1) We can reference any persistent type by this way
2) Our queries aren't bound to a particular session. Instead, we resolve it on each execution usong Session.Demand() method (or Current property) relying on our context-scope pattern.

2. We use context-scope pattern and PostSharp aspects maintaining "correct" Session.Current everywhere.

You shouldn't pass current Session everywhere, because Session.Current is automatically set for duration of any public method call on SessionBound ancestor - this is done by our aspects, and this is really cheap.

So how to implement DataContext-like repository for DO4?

Just add properties like this one to your own Repository-like type:

public IQueryable{Customer} Customers {
  get { return Query{Customer}.All; }

Such properties, as well the whole type, can be static. That's it ;)

No comments:

Post a Comment