Ayende @ Rahien

It's a girl

NHibernate and Generic Entities

Colin Jack has brought up the question of generic entities, which is something that I consider fairly odd use case. Generics are very useful, but not in the final entity layer (they are very useful as layer super type, though).

First, let us understand what I am are talking about when I am thinking about generic entities:

image

The generic entity that you see is ContactInformation<TContactInfoType>, and we want to use several specialized versions of that in our application. In this case, we have contact that can be either a string or a user.

Here is an interesting tidbit, NHibernate supports this, here it the required configuration:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
				   assembly="Blog"
				   namespace="MyBlog">
	<class name="ContactInformation`1[System.String]"
		   table="ContactInformation_String">
		<id name="Id">
			<generator class="identity"/>
		</id>
		<property name="Contact" type="System.String"/>
	</class>

	<class name="ContactInformation`1[MyBlog.User]"
		 table="ContactInformation_User">
		<id name="Id">
			<generator class="identity"/>
		</id>
		<many-to-one name="Contact" class="MyBlog.User, Blog"/>
	</class>
</hibernate-mapping>

We can even do different associations to different classes, simple column for string, FK for user.

What we can't do is to map ContactInformation<TContactInfoType>, for the very simple reason that we have no way of doing it. Consider the example above, in one case, we are using a FK, in the other, a simple column. NHibernate has no way of guessing, and since I can't really think of a good reason to want to start mapping random values to my database, I think that NHibernate is doing a good thing by insisting that you tell it about how you want this mapped.

There may be a problem with querying this with HQL, but I think this can be gotten around to with importing the class name to an alias.

Now, let us get back to the diagram below, by show of hand (well, comments), how many of you think that this is a good way to design your entities?

An entity is not a list, to which you shove random data. It has a meaning in the domain, and it has a table in which you put the data, and you want to query it, etc. It would take some pretty peculiar use case to convince me that there isn't a really simplifying factor here by simply introducing SimpleContactInformation and UserContactInformation classes, which would simple derived from the generic class with the appropriate parameters. The ability to use generics is nice to cut duplicate code, but it also obstruct the clarity of the code, so I see this as significant added value.

In short, it is possible, but don't do it.

Comments

Gary
11/14/2007 01:29 AM by
Gary

Thanks for the tidbit!

Whilst generics aren't something I'd put throughout the domain layer there are times that I would do it, for instance (and these are probably the only 2 times that I would have done this):

  • A generic Range value object (public class Range where T : IComaprable{})... robably mapped as a component in NHibernate.

  • And a genric TemporalProperty association object (see http://martinfowler.com/eaaDev/TemporalProperty.html) where I want to hide the fact from the consumer that I am utilising a collection internally)

So in response to your request for a show of hands... I'll put one hand up and the other one down :-)

Sean Chambers
11/14/2007 03:18 AM by
Sean Chambers

I would personally think this would be handy to do, but would not do it myself.

The reason being is, by simply looking at the digram above it doesn't convey the meaning that well.

As you state it is possible to do this mapping with nhibernate which is great and I'm sure over a long enough time I could find a use for it, but unless I was sure that doing it this way would give me a huge benefit it would make me feel dirty.

Generics are very useful but I think people tend to go overboard with them trying to use them in instances where it's not warranted

Fabio Maulo
11/14/2007 06:40 AM by
Fabio Maulo

When entity-name feature will be ported, probably, will be more easy to query generic entities too

(ref to http://www.hibernate.org/hib_docs/reference/en/html/mapping.html

5.1.3)

Another possible use of generics entities is the use of mapping (with all it implies).

I'm not secure but I think that, in the feature, we can think some solution and possible application of generics-entities, with a possible implementation, using special tuplizers (ref http://www.hibernate.org/hib_docs/reference/en/html/persistent-classes.html#persistent-classes-dynamicmodels)

Colin Jack
11/14/2007 08:33 AM by
Colin Jack

I agree with you about generics and I wasn't pushing for a mass adoption of generics in the domain. However as Gary says using them in the domain can sometimes be useful.

As you say having a generic contact information class is probably not what you want. However for something like a Range or another class in that mould having to create subclasses is a little bit nasty. Another example I can think of is where T is going to be one of more thanenum, though I'm not sure if we've ever met that case.

I'm all for expressing intent in my designs but we've definitely found a few cases where in order to just get something mapped we've had to create the non-generic subclasses despite the fact that we don't believe they've improved the design at all.

So in actual fact these non-generic subclasses (which in one case I believe aren't even exposed outside the domain) are just adding the to the conceptual burden of understanding the domain.

Anyway ta for the response, as always it was very useful.

Alan Buck
11/15/2007 01:10 AM by
Alan Buck

This is just an inquiry. I have admired your UML? diagrams and wonder what you are using to produce them. I would like to use the same tool myself.

Ayende Rahien
11/15/2007 01:13 AM by
Ayende Rahien

Visual Studio Class Diagrams

zahra karimi
11/16/2007 04:56 AM by
zahra karimi

have build this example and it works until the session.Save(newUser)

when an exception occures.

NHibernate.MappingException "Unknown Entry Class: User"

Code:

        Configuration cfg = new Configuration();

        cfg.AddAssembly("nhibernate");


        ISessionFactory factory = cfg.BuildSessionFactory();

        ISession session = factory.OpenSession();

        ITransaction transaction = session.BeginTransaction();


        User newUser = new User();

        newUser.Id = "joe_cool";

        newUser.UserName = "Joseph Cool";

        newUser.Password = "abc123";

        newUser.EmailAddress = "joe (at) cool (dot) com";

        newUser.LastLogon = DateTime.Now;


        session.Save(newUser);

        transaction.Commit();

        session.Close();

User.cs

using System;

public class User

{

private string id;

private string userName;

private string password;

private string emailAddress;

private DateTime lastLogon;


public User()

{

}


public string Id

{

    get { return id; }

    set { id = value; }

}


public string UserName

{

    get { return userName; }

    set { userName = value; }

}


public string Password

{

    get { return password; }

    set { password = value; }

}


public string EmailAddress

{

    get { return emailAddress; }

    set { emailAddress = value; }

}


public DateTime LastLogon

{

    get { return lastLogon; }

    set { lastLogon = value; }

}

}

User.hbm.xls

<?xml version="1.0" encoding="utf-8" ?>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">

<id name="Id" column="LogonId" type="String" length="20">

  <generator class="assigned" />

</id>

<property name="UserName" column="Name" type="String" length="40"/>

<property name="Password" type="String" length="20"/>

<property name="EmailAddress" type="String" length="40"/>

<property name="LastLogon" type="DateTime"/>

</hibernate-mapping>

if anyone can assist me, it would be greatly apreciated

Ayende Rahien
11/16/2007 05:02 AM by
Ayende Rahien

The file name should be User.hbm.xml, and it should be an embedded resource

Comments have been closed on this topic.