Mike,
Couldn't it be implemented on a 'per-request' method? Here is what a co-worker wrote me on the matter (I think it makes sense as well):
To have per-request (i.e. safe per-thread) data under ASP.NET, you can use HttpContext.Current. That object has an Items collection - essentially a per-request dictionary that you can store values in. You just need to come up with a string-based key for each.
Current is an object of type HttpContext. The doc says that it "encapsulates all HTTP-specific information about an individual HTTP request". From what I've read (and done occasionally in the past), it's the safe way to do per-thread "global" data, since ordinary .NET thread-local-storage doesn't work properly in an ASP.NET environment.
The one catch is this: if you're testing your code outside of running it on a web server (e.g. unit tests), then HttpContext.Current is null! To avoid exceptions being thrown, you have to do something like this:
using System.Web;
// ...
class SomeObject
{
// ...
[ThreadStatic]
private static SomeObject instance; // used when HttpContext.Current == null
// otherwise, the instance is stored in
// HttpContext.Current.Items
private static string index = "SomeObject";
protected SomeObject()
{
// ...
}
public static SomeObject Instance
{
get
{
SomeObject instance;
if (HttpContext.Current == null)
{
instance = SomeObject.instance;
if (instance == null)
SomeObject.instance = instance = new SomeObject();
}
else
{
instance = (SomeObject)HttpContext.Current.Items[index];
if (instance == null)
HttpContext.Current.Items[index] = instance = new SomeObject();
}
return instance;
}
}
// ...
}
Like I said, the EntitySpaces developer is assuming that the identity map is application-wide. I think that would be a mistake, due to all the locking issues required (as he rightly points out). If the identity map is per-request, then his main concern is not actually a problem.
The performance overhead of accessing the hash table is probably small, compared to the cost of database access. In some cases (e.g. redundantly loading an individual object), it actually *saves* you a trip to the DB (it doesn't save a trip when loading a collection, though, but it could still save memory because redundant objects won't be instantiated).
If the performance hit of indexing based on composite primary keys is a problem, then the answer is simple: don't use composite primary keys! I can't think of a good reason to use composite primary keys for tables whose rows correspond to domain entities anyway. You'll always want them to have int keys. You mainly see composite keys in association tables (i.e. many-to-many mappings), but rows in association tables don't correspond to domain entities.
The final concern is that the identity maps take up a lot of memory due to all the objects they store. If the maps are per-request, I think this is a total non-issue.
Sure, objects take up memory and must eventually be garbage-collected, but this is true of EntitySpaces objects now anyway. They don't need to be stored post-request.
I suppose that, if you really wanted to, you could use the ASP.NET cache to (possibly) cache commonly accessed objects across requests (then you'd need some locking to synchronize concurrent accesses), but that's a side issue.
Last: the developers could add support for identity maps in the same way as they've added hierarchical models: as a feature you can turn on or off at code generation time.
That way, projects that don't need it don't use it.
It's true that many projects probably don't need identity maps. But, for those projects that do make use of large, complex domain models, this could be a big weakness of on the part of entityspaces. How could they possibly build a network of inter-referenced objects, where there can be more than one reference to the same object?