Tuesday, January 24, 2012

Mapping-by-Code - OneToMany and other collection-based relation types

This post is going to be a continuation for the previous one, about Set and Bag mappings. Previously I've described collection and key column mappings. This time I'll cover mapping part that defines the relation type the collection takes part in.

There are five relation types supported by collections. I'll list it with its HBM names:

  • one-to-many - when the collection elements are entities
  • many-to-many - same, but storing the relation in separate table to allow m:n relations
  • many-to-any - heterogenous association with entities of different types
  • element - when the collection elements are single-column value types
  • composite-element - when the collection elements are multiple-column value types (components)

In mapping-by-code, the relation type is defined in the third parameter of Set/Bag mapping. It is optional, with default one-to-many. There's a method for every relation type. Let's go through that methods one by one - I'll show only the lambda from third Set/Bag method parameter.

The first one is OneToMany, for one-to-many entity mapping, obviously.

r => r.OneToMany(m =>
{
m.NotFound(NotFoundMode.Exception); // or NotFoundMode.Ignore
m.Class(typeof(CustomType));
m.EntityName("entityName");
})

It has an optional parameter with configuration. NotFound defines the NHibernate behavior when the referenced entity is missing in the database. Class and EntityName allows to set up the relation for non-standard other side mappings.

Next is ManyToMany. The main difference is how the relation is stored in the database. ManyToMany relation needs an intermediate table with foreign keys to allow m:n relations. There are several options available affecting how the additional table looks like.

r => r.ManyToMany(m =>
{
m.Column("otherKeyColumnName");
// or
m.Column(c =>
{
c.Name("otherKeyColumnName");
// etc...
});

m.ForeignKey("otherKey_fk");
m.Formula("arbitrary SQL expression");
m.Lazy(LazyRelation.Proxy); // or LazyRelation.NoProxy or LazyRelation.None
m.NotFound(NotFoundMode.Exception); // or NotFoundMode.Ignore

m.Class(typeof(CustomType));
m.EntityName("entityName");
})

Configuration parameter of ManyToMany is optional - it may be skipped if we leave all options with default values and set the naming through the convention. In the options there is standard Column method that allows to define name and other DDL-level properties of the key column referencing other side entity (note that we've defined key column for our entity in bag/set mapping options) - it is useful if we don't map the other side and still be able to generate the tables properly. We can also set up laziness through Lazy method, behaviour for not found rows through NotFound or even set up the relation using arbitrary SQL expression instead of foreign key column value using Formula method.

Third one is ManyToAny. This is quite an exotic feature of NHibernate, but there are some cases where it's really useful. See Ayende's post for detailed description. Generally, this is for the case when we have a many-to-many relation with entitles of different types at the other side. NHibernate needs to be said how to distinguish the type of entity and is able to query the proper tables for different objects. Let's stick to Ayende's example:

r.ManyToAny<long>(m =>
{
m.Columns(id =>
{
id.Name("PaymentId");
id.NotNullable(true);
// etc...
}, classRef =>
{
classRef.Name("PaymentType");
classRef.NotNullable(true);
// etc...
});

m.IdType<long>(); // redundant, needs to be specified in ManyToAny parameter
m.MetaType<string>();

m.MetaValue("CreditCard", typeof(CreditCardPayment));
m.MetaValue("Wire", typeof(WirePayment));
})

The generic type in ManyToAny method defines the common type for identifiers of all entities at the other side. Inside the configuration (which is required in this case), we need to define properties for two columns this time - one to keep the other entity identifier, second to keep its discriminating value. We do it using Columns method's parameters. Later we have to specify the type of discriminator using MetaType method and its generic argument - string is good here. We can also specify the common type of identifiers using IdType method, but we've already did it in ManyToAny generic parameter (I think that this method is useless here). The last thing we need to do is to define the list of entity types that are allowed at other side of the relation and its corresponding discriminator values. In order to do this, we call MetaValue method - its first parameter is the discriminator value, second is the type.

The next collection-based relation type available is Element. This is designed for collection of simple value-typed objects, i.e. list of strings.

r => r.Element(m =>
{
m.Column("valueColumnName");
// or
m.Column(c =>
{
c.Name("valueColumnName");
// etc...
});

m.Formula("arbitrary SQL expression");
m.Length(100);
m.NotNullable(true);
m.Precision(10);
m.Scale(10);
m.Type<CustomType>(parameters);
m.Unique(true);
})

The options available are quite standard - there are DDL options for value column available within Column method and different Property-like options describing the value itself. Note that foreign key column options or table options are defined in collection options.

The last possible relation type for collection mapping is Component, known in XML as composite-element. Mapping-by-Code merged these two terms into component, because there is no real difference besides the fact that components were parts of single objects and composite elements were used in collections only.

r => r.Component(m =>
{
m.Property(x => x.Name);
// etc...
})

The mapping itself is like already described component mapping, so I'll skip it here.

Fluent NHibernate's equivalents

As I've already described in the previous post, Fluent NHibernate is not separating the collection mapping from the relation mapping, mixing it together in one method chain. Many-to-any relation is not supported by FNH, and the remaining four types of relations are mapped differently.

Let's go through the mappings - I'll skip the options regarding collection mapping and reflect only these options, that are part of relation mappings in mapping-by-code to keep the comparison consistent.

The first one is HasMany for one-to-many relation:

HasMany(x => x.Users)
.NotFound.Ignore() // or .Exception()
.EntityName("entityName");

HasManyToMany is for many-to-many:

HasManyToMany(x => x.Users)
.ChildKeyColumn("otherKeyColumnName")
.ForeignKeyConstraintNames("parentForeignKeyName", "childForeignKeyName")
.NotFound.Ignore() // or .Exception()
.EntityName("entityName");

Formula mapping is missing. Foreign key name configuration is joined for both sides. There are few more options regarding "child" (other entity) key column, all with names starting with Child.

Many-to-any relation is not supported in Fluent NHibernate.

The next one is element relation, merged into HasMany method, available in the chain through Element method:

HasMany(x => x.Users)
.Element("valueColumnName", m =>
{
m.Formula("arbitrary SQL expression")
.Length(100)
.Type<CustomType>();
})

Other element relation options are not supported.

And finally, there is composite element (component) mapping, named Component here, too. It is also merged into HasMany chain.

HasMany(x => x.Users)
.Component(m =>
{
m.References(x => x.Name);
// etc...
})

10 comments:

  1. Great posts... Really hard to find any info on mapping by code.

    I've been trying to choose FHN or mapping by code for my next project and find both seem equal in the pros and cons... Being a total noob to nh would u recommend one over the other?

    ReplyDelete
  2. Thanks!

    A month ago I would still recommend FNH as it looked more mature and there are a lot of examples, but now, after spending quite a lot of time with mapping-by-code, I find it quite powerful. Big advantage is that it is more efficient (no XML serialization) and more coupled to NHibernate itself - no additional dependency. And the lack of examples hopefully is fixed a bit by my series. I'll go with mapping-by-code now.

    ReplyDelete
  3. Thanks for the advice Adam... mapping by code seems to be the sensible option going forward. I just need to convince my team its the best option.

    Keep the posts coming! :)

    ReplyDelete
  4. Great summary, but there is still so many questions for a beginners (like me!)

    How do I map a one to many relationship where the parent table's primary key (which is a composite key, and a component) is the foreign key (and primary key) in the child table?!?

    Is it possible?

    http://stackoverflow.com/questions/12836288/nhibernate-3-3-mapping-one-to-many-relationship-with-composite-foreign-keys

    ReplyDelete
    Replies
    1. Well, that's extremely exotic case, I wouldn't be surprised if it is not supported. Anyway, this is not one-to-many for sure, try with one-to-one.

      Delete
    2. Aaah you are right. Off the top you head, do you know if a Many to Many relationship with composite keys is currently being supported? I might have to drop mapping-by-code if this doesn't work!

      Delete
    3. I haven't tried, but it should. Try with x => x.ManyToMany(r => r.Columns(...))

      Delete
  5. Would it be possible for you to post your code, please?

    Thank you.

    ReplyDelete
  6. The fourth paragraph starts with these lines: "In mapping-by-code, the relation type is defined in the third parameter of Set/Bag mapping. It is optional, with default one-to-many."

    Maybe it's changed since the article was written, but I found that I could not leave out the "r => r.OneToMany()" relation specifier for a Set() call, or things would not work. Just a warning to future readers.

    ReplyDelete