Showing posts with label one-to-many. Show all posts
Showing posts with label one-to-many. Show all posts

Sunday, April 15, 2012

NHibernate's inverse - what does it really mean?

NHibernate's concept of 'inverse' in relationships is probably the most often discussed and misunderstood mapping feature. When I was learning NHibernate, it took me some time to move from "I know where should I put 'inverse' and what then happens" to "I know why do I need 'inverse' here and there at all". Also now, whenever I'm trying to explain inverses to somebody, I find it pretty hard.

There are a lot of explainations over the net, but I'd like to have my own one. I don't think that the others are wrong, it'll just help me arrange my own understanding and if anyone else take advantage of this, that's great.

Where do we use inverse?

First, some widely-known facts, next we'll elaborate on few of them.

  • Inverse is a boolean attribute that can be put on the collection mappings, regardless of collection's role (i.e. within one-to-many, many-to-many etc.), and on join mapping.
  • We can't put inverse on other relation types, like many-to-one or one-to-one.
  • By default, inverse is set to false.
  • Inverse makes little sense for unidirectional relationships, it is to be used only for bidirectional ones.
  • General recommendation is to use inverse="true" on exactly one side of each bidirectional relationship.
  • When we don't set inverse, NHProf will complain about superfluous updates.

What does it mean for a collection to be 'inverse'?

The main problem in understanding 'inverse' is it's negating nature. We're not used to setting something up in order to NOT take an action. Inverse set to true means "I do NOT maintain this relationship". Hence, inverse set to false means "I DO maintain this relationship".

It'll be much more understandable if we could go to the opposite side of the relationship and be positive there: "This side maintains the relationship" and NHibernate would automatically know that the other side doesn't (*). But it is implemented as it is - we have to live with inverse's negative character.

Each relationship is represented in the database as an identifier of a related table row in the foreign key column at 'many' side. Why at 'many' side? Because that's how we do relationships in the relational databases. The column "holding" the association is always at 'many' side. It's not possible to keep the association at 'one' side because we'd have to insert many values into one database field somehow.

So what does it mean for a collection in NHibernate to maintain the relationship (inverse="false")? It means to ensure that the relation is correctly represented in the database. If the Comments collection in the Post object is responsible for maintaining the relationship, it has to make sure all its elements (comments) have foreign keys set to post's id. In order to do that, it issues a SQL UPDATE statement for each Comment, updating its Post reference. It works, the relationship is persisted correctly, but these updates often do not change anything and can be skipped (for performance reasons).

Inverse="true" on a collection means that it should not take care whether the foreign keys in the database are properly set. It just assumes that some other party will take care of it. What do we gain? We have no superfluous UPDATE statements. What can we lose? We have to be sure that the second side actually takes over the responsibility of maintaining the association. If it doesn't, nobody will and we'll be surprised that our relationship is not persisted at all (NHibernate will not throw an error or so, it won't guess that it's not what we've expected).

When should we set inverse="true"?

Let's consider one-to-many first. Our relationship must be bidirectional and have entities (not value types) at both sides for inverse to make sense. Other side ('many' side) is always active, we can't set inverse on many-to-one. This means that we should put inverse="true" on the collection, provided that:

  • our collection is not explicitly ordered (like <list>) - it is i.e. <bag> or <set>; ordered lists have to be active in order to maintain the ordering correctly; 'many' side doesn't know anything about the ordering of collection at 'one' side
  • we actually set the relationship at 'many' side correctly

Consider the example:

public class Post
{
public virtual int Id { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}

public class Comment
{
public virtual int Id { get; set; }
public virtual Post Post { get; set; }
public virtual string Text { get; set; }
}

// ...

var comment = new Comment() { Text = "the comment" };
session.Persist(comment);
post.Comments.Add(comment);

We are not setting Post property in Comment class as we may expect NHibernate will handle that as we append our comment to the collection of comments in particular Post object (**). If the post.Comments collection is not inverse, it will actually happen, but quite ineffectively:

We've inserted null reference first (exactly as it was in our code) and then, as the collection is responsible for maintaining the relationship (inverse="false"), the relationship was corrected by separate UPDATE statement. Moreover, in case we have not null constraint on Comment.Post_id (which is actually good), we'll end up with exception that we can't insert null foreign key value.

Let's see what happens with inverse="true":

There's no error, but the comment is actually not connected to the post, despite we've added it to a proper collection. But using inverse, we've explicitly turned off maintaining the relationship by that collection. And as we don't set the relationship on Comment side, noone does.

The solution of course is to explicitly set comment's Post property. It is good from object model perspective, too, as it reduces the amount of magic in our code - what we've set is set, what we haven't set is not set magically.

var comment = new Comment() { Text = "the comment", Post = post };
session.Persist(comment);
post.Comments.Add(comment);

Much better now:

Time for many-to-many. Again, inverse makes sense only when we've mapped both sides. We have to choose one side which is active and mark the second one as inverse="true". Without that, when both collections are active, both try to insert a tuple to an intermediate table many-to-many needs. Having duplicated tuples makes no sense in most cases. For some suggestions how to choose which side is better in being active, see my post from December.

To sum up

Left sideRight sideInverse?
one-to-manynot mappedmakes no sense - left side must be active
one-to-manymany-to-oneright side should be active (left with inverse="true"), to save on UPDATEs
(unless left side is explicitly ordered)
many-to-manynot mappedmakes no sense - left side must be active
many-to-manymany-to-manyone side should be active (inverse="false"), the other should not (inverse="true")

______

(*) There are of course reasons why NHibernate doesn't do assumptions about other sides of relationships like that. The first one is to maintain independence between mappings - it will be cumbersome if change in mapping A modifies the B behaviour. The second one are ordered collections, like List. The ordering can be automatically kept by NHibernate only when collection side is active (inverse="false"). If the notion of being active is managed on the other side only, changing the collection type from non-ordered to ordered would require changes in both mappings.

(**) Note that inverse is completely independent from cascading. We can have cascade save on collection and it does not affect which side is responsible for managing the relationship. Cascade save means only that when persisting Post object, we're also persisting all Comments that were added to the collection. They are inserted with null Post value and UPDATEd later or inserted with proper value in single INSERT, depending on object state and inverse setting, as described above.

Monday, February 6, 2012

Mapping-by-Code - Map

The last not yet covered collection type supported in NHibernate 3.2 mapping-by-code is Map. I'll skip all the options that are common with set and bag and refer to its own post. For other collection types, see also the previous post.

Map is NHibernate's name for dictionary, or key-value collection. It is quite a powerful feature. NHibernate allows keys and values to be of different types - either single elements (like strings), components or entities. We can have an entity in the key and string in the value or string in the key and component in the value, etc. Moreover, as with every other collection, map can take part in one-to-many, many-to-many and even many-to-any relationships. There's quite a lot of options.

Mapping for all of these options must be scary, one may think. But not with mapping-by-code. I'm really impressed how easy, convenient and consistent with all the other features the Map mapping is. There's an recurring pattern in mapping-by-code that each nested element in XML has its corresponding options as a separate method parameter. And that's the case for Map, too. There are three basic overloads for Map method:

public void Map<TKey, TElement>(
Expression<Func<TEntity, IDictionary<TKey, TElement>>> property,
Action<IMapPropertiesMapper<TEntity, TKey, TElement>> collectionMapping);

public void Map<TKey, TElement>(
Expression<Func<TEntity, IDictionary<TKey, TElement>>> property,
Action<IMapPropertiesMapper<TEntity, TKey, TElement>> collectionMapping,
Action<ICollectionElementRelation<TElement>> mapping);

public void Map<TKey, TElement>(
Expression<Func<TEntity, IDictionary<TKey, TElement>>> property,
Action<IMapPropertiesMapper<TEntity, TKey, TElement>> collectionMapping,
Action<IMapKeyRelation<TKey>> keyMapping,
Action<ICollectionElementRelation<TElement>> mapping);

Don't be scared. These are just three variants of the same method ordered from the simplest one (with two parameters skipped - it'll have its default values) to the most complete one (with all four parameters). Let's focus on the last one.

The first parameter is - as always - the lambda expression pointing to a generic IDictionary property we are mapping. The second is an obligatory collection mapping - all the standard collection options (described in the post about Bag and Set) are available there. The third one, optional, is to set options for dictionary key mapping and the last one, also optional, is about its value mapping. Here it is, with all its options:

Map(x => x.Features, c =>
{
// standard collection options here
}, k =>
{
k.Element(e =>
{
e.Column("indexColumnName");
// or
e.Column(c =>
{
c.Name("indexColumnName");
// etc...
});

e.Formula("arbitrary SQL expression");
e.Length(10);
e.Type<CustomType>();
});
// or
k.Component(e =>
{
e.Property(x => x.KeyElement);
e.ManyToOne(x => x.OtherKeyElement);
// etc...
});
// or
k.ManyToMany(e =>
{
e.Column("indexColumnName");
// or
e.Column(c =>
{
c.Name("indexColumnName");
// etc...
});

e.ForeignKey("foreignKeyName");
e.Formula("arbitrary SQL expression");
});
}, r =>
{
// one of the following:
r.OneToMany();
r.Element();
r.Component(m => {});
r.ManyToMany();
r.ManyToAny<CommonIdType>(m => {});
});

Let's talk about key and value mappings (third and fourth parameter). In XML, there are plenty of different names for all its options, like map-key, composite-map-key, map-key-many-to-many etc. Mapping-by-code simplifies it drastically. We just need to choose what type of element our key is - either Element (for simple values), Component or ManyToMany (for cases when we have an entity as a key). We are already familiar with all the options inside. The same is for value mapping - we need to choose one of five possible relation types, depending on what do we have as a value in our dictionary. For the description of different relation types, see separate post - all the options are available here, too.

Moreover, the default ConventionModelMapper is smart enough to figure out most options just by looking at our model and in most cases we just don't need to specify the relation types and its options at all.

Fluent NHibernate's equivalent

In Fluent NHibernate, the name "Map" was already used for Property mapping. Instead we have several different methods in HasMany/HasManyToMany chains to be used - AsMap, AsEntityMap, AsSimpleAssociation, AsTernaryAssociation. Pretty hard to figure out what's what.

Mapping for the dictionary value and its options stays the same as in Bag/Set case. I'll focus on mapping different key types.

The first case is when we have simple value as a key - like IDictionary<string, string>:

HasMany(x => x.Dictionary)
.AsMap<string>("keyColumn")
.Element("valueColumn");

Majority of AsMap overloads want me to specify lambda expression pointing from the value to the key and internally use AsIndexedCollection method (designed for List, I believe). These methods seems to assume that key is a part of an object in value, what is strange a bit. In that case we don't really need to have a dictionary and we could just map that collection as a simple bag.

Moreover, FNH is not trying to determine the key type on its own - we need to specify it as a generic parameter in AsMap method explicitly, otherwise we'll end up with int. I also can't see the way to set other key options available in mapping-by-code, i.e. Length or Formula.

The second case I've tried was having a component as a key - and I've failed. I was looking through the Web and the source code itself and I can't see no composite-index equivalent.

Third type of objects allowed as a key is another entity - i.e. IDictionary<Entity, string>. We can map it using AsEntityMap:

HasMany(x => x.Dictionary)
.AsEntityMap()
.Element("valueColumn");

I was looking for any clues when should I use AsTernaryAssociation or AsSimpleAssociation directly, but I don't find any.

To sum things up, dictionary mapping in Fluent NHibernate is a horrible mess. I've found a comment in FNH source code: "I'm not proud of this. The fluent interface for maps really needs to be rethought. But I've let maps sit unsupported for way too long so a hack is better than nothing." Well, personally I'm not sure whether it'll be better in this case. Mapping by code is way much better this time.

Friday, February 3, 2012

Mapping-by-Code - List, Array, IdBag

Time to complete the subject of collection mappings - there are some features unique for lists and idbags that need to be mentioned. I'll skip all the options that are common with set and bag and refer to its own post.

List is an explicitly ordered collection. It has an additional option in the mapping for the index column that keeps the ordering, with the standard Column options mapping inside.

List(x => x.Districts, c =>
{
c.Index(idx =>
{
idx.Base(1);
idx.Column("indexColumnName");
// or
idx.Column(ic =>
{
ic.Name("indexColumnName");
// etc...
});
});
});

There are two other rarely used ordered collections in NHibernate - arrays and primitive arrays. Both are not supported in mapping-by-code. But I don't think it's a big deal.

Another collection type is IdBag. It is generally a bag enriched with additional identity column - useful for many-to-many intermediate tables, when we don't want to have composite primary key there. The only new option in the mapping is Id method, with several options available:

IdBag(x => x.Collection, c =>
{
c.Id(i =>
{
i.Column("idColumn");
i.Generator(Generators.Native);
i.Length(100);
i.Type(new Int32Type());
});
}, r => r.ManyToMany());

Fluent NHibernate's equivalents

As we already know, FNH defines collection mappings as a part of relation mapping chain. I'll skip all the options already covered in the Bag/Set post.

List is mapped using AsList method. There is a possibility to set index column name and type - other DDL options, as well as base attribute are missing.

HasMany(x => x.Users)
.AsList(idx => idx.Column("indexColumnName").Type<int>())

Array is also supported, contrary to mapping-by-code. It has similiar possibilities to the list mapping, the only difference is that we can define index column name by lambda parameter, but it's used only if we don't specify it in the second parameter explicitly.

HasMany(x => x.Users)
.AsArray(x => x.Name, idx => idx.Column("indexColumnName").Type<string>())

Primitive arrays and idbags are not supported.

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...
})

Saturday, January 21, 2012

Mapping-by-Code - Set and Bag

It's time for a huge topic - collections. The original Ayende's post was about <set> in the context of one-to-many relationship. I'm going to change it a bit and start with showing how to map sets and bags using mapping-by-code vs. Fluent NHibernate, ignoring the relation context (i.e. one-to-many or many-to-many) - it is clearly separated both in XML mapping and in mapping-by-code. I'll describe different relation types mappings handled by sets and bags in the separate post.

Contrary to Fluent NHibernate and likewise XML mapping, the collection type is a starting point for the mapping. The options are exactly the same in <set> and <bag>, they differ only with the method name - Set vs. Bag. I'll show Set here, for Bag just change the method called in the first line.

Set(x => x.Users, c =>
{
c.Fetch(CollectionFetchMode.Join); // or CollectionFetchMode.Select, CollectionFetchMode.Subselect
c.BatchSize(100);
c.Lazy(CollectionLazy.Lazy); // or CollectionLazy.NoLazy, CollectionLazy.Extra

c.Table("tableName");
c.Schema("schemaName");
c.Catalog("catalogName");

c.Cascade(Cascade.All);
c.Inverse(true);

c.Where("SQL command");
c.Filter("filterName", f => f.Condition("condition"));
c.OrderBy(x => x.Name); // or SQL expression

c.Access(Accessor.Field);
c.Sort<CustomComparer>();
c.Type<CustomType>();
c.Persister<CustomPersister>();
c.OptimisticLock(true);
c.Mutable(true);

c.Key(k =>
{
k.Column("columnName");
// or...
k.Column(x =>
{
x.Name("columnName");
// etc.
});

k.ForeignKey("collection_fk");
k.NotNullable(true);
k.OnDelete(OnDeleteAction.NoAction); // or OnDeleteAction.Cascade
k.PropertyRef(x => x.Name);
k.Unique(true);
k.Update(true);
});

c.Cache(x =>
{
x.Include(CacheInclude.All); // or CacheInclude.NonLazy
x.Usage(CacheUsage.ReadOnly); // or CacheUsage.NonstrictReadWrite,
// CacheUsage.ReadWrite, CacheUsage.Transactional
x.Region("regionName");
});

c.SqlDelete("SQL command");
c.SqlDeleteAll("SQL command");
c.SqlInsert("SQL command");
c.SqlUpdate("SQL command");
c.Subselect("SQL command");
c.Loader("loaderRef");
}, r =>
{
// one of the relation mappings (to be described separately)
r.Element(e => { });
r.Component(c => { });
r.OneToMany(o => { });
r.ManyToMany(m => { });
r.ManyToAny<IAnyType>(m => { });
});

Whoa! The first parameter, not surprisingly, is the lambda expression for the collection property we're mapping. Second one allows to configure the set/bag using a bunch of options. Third one, optional, defines the type of relation the collection takes part in - one-to-many by default (I'll write about relation types separately).

Let's see what options set and bag have to offer and how it differs from XML mappings.

Fetch, BatchSize and Lazy define how and when the collection is loaded from the database. Table, Schema and Catalog say where to look for the collection data in the database. Inverse is useful for bidirectional relationships and defines which side is responsible for writing.

Cascade tells how operations on the entity affects the collection elements. Note that in mapping-by-code it is redefined a bit. Here are the possible values and its corresponding XML values (note that All still doesn't mean literally all values; DeleteOrphans still needs to be specified aside).

[Flags]
public enum Cascade
{
None = 0, // none
Persist = 2, // save-update, persist
Refresh = 4, // refresh
Merge = 8, // merge
Remove = 16, // delete
Detach = 32, // evict
ReAttach = 64, // lock
DeleteOrphans = 128, // delete-orphans
All = 256, // all
}

Combining the values can be done using logical | operator or using syntax provided through extension methods:

Cascade.All.Include(Cascade.DeleteOrphans);

Coming back to bag/set options. Where, Filter and OrderBy affect what data are loaded from the database. Where and OrderBy allows to specify any SQL expression for narrowing and sorting the collection at database level. Furthermore, OrderBy has an overload that makes it possible to specify the ordering by an expression, too. Filter is even more powerful - see another Ayende's post for an explanation.

All the options above, up to Key call in my example, have corresponding XML attributes in <bag> or <set> XML element. Key and all further options are defined in XML as separate elements inside <bag> or <set>.

Key is to define key column in the table that holds collection elements - there are some DDL parameters and behaviors configurable. Key method is a direct equivalent of mandatory <key> element in XML.

Cache controls the second level cache behaviors for the collection. It's an equivalent of <cache> element in XML.

All the SqlXYZ methods are to set up custom ways of reading and writing collection to the database and all of it have its corresponding XML elements, too - useful if we have to use stored procedures for data access. In XML mappings, there's also an ability to tell NHibernate to check what was returned from the procedure, but it seems not supported by mapping-by-code.

The only attribute of Set and Bag that is not configurable in mapping-by-code is generic. It is to determine whether the collection type used is generic. But by default, NHibernate checks this on its own by reflection and I can't see why anyone would need to override this behavior.

Fluent NHibernate's equivalent

Fluent NHibernate redesigned the approach established by XML mappings and made a relation type an entry point (and only required part) of the mapping. Moreover, only relations with entities at the other side are considered "first-class citizens" by FNH. So there are only two entry methods: HasMany and HasManyToMany. Element and component (composite element) mappings are hidden inside HasMany as its options. Many-to-any seems to be not supported.

As I'm not describing relationship types in this post, I'll pick many-to-one as an example just to have an entry point and I'll skip the options connected with relationship itself, focusing on collection and key column options. This way it'll cover about the same options as in mapping-by-code example.

HasMany(x => x.Users)
.AsSet<CustomComparer>() // or .AsSet(), .AsBag()
.Fetch.Join()
.BatchSize(100)
.LazyLoad() // or .ExtraLazyLoad()
.Table("tableName")
.Schema("schemaName")
.Cascade.AllDeleteOrphan() // or .None(), .SaveUpdate(), .All(), DeleteOrphan()
.Inverse()
.Where("SQL command") // or an boolean lambda expression
.ApplyFilter("filterName", "condition")
.OrderBy("SQL expression")
.Access.Field()
.CollectionType<CustomType>()
.Persister<CustomPersister>()
.OptimisticLock.Version() // buggy
.ReadOnly()
.Generic()
.KeyColumn("columnName")
.ForeignKeyConstraintName("collection_fk")
.Not.KeyNullable()
.ForeignKeyCascadeOnDelete()
.PropertyRef("propertyRef")
.KeyUpdate()
.Subselect("SQL command")
.Cache.IncludeAll() // or .IncludeNonLazy, .CustomInclude("customInclude")
.ReadOnly() // or .NonStrictReadWrite(), .ReadWrite(), .Transactional(), .CustomUsage("customUsage")
.Region("regionName");

List of possible options is quite long, too. Let's go through and note the major differences.

AsSet method needs to be called to change the default collection type (bag) to set. I've already mentioned that I don't like the fact that such a fundamental thing like collection type is just an ordinary, optional switch in FNH. Moreover, the overloads with comparer (being equivalent of Sort from mapping-by-code) make AsSet method look like the comparer is the only sense of its existence. And that's obviously not the case.

Several options have names changed in FNH:

  • Filter and its Condition are merged into ApplyFilter,
  • Type is CollectionType this time (instead of FNH's standard CustomType),
  • Column is KeyColumn,
  • ForeignKey is ForeignKeyConstraintName,
  • NotNullable is Not.KeyNullable,
  • OnDelete(OnDeleteAction.Cascade) is mapped by ForeignKeyCascadeOnDelete,
  • Update is KeyUpdate,
  • and finally, quite confusing, Mutable is ReadOnly in FNH. It wouldn't be so surprising if FNH didn't use ReadOnly as a shortcut for Not.Update and Not.Insert in other mappings. And Mutable is something different.

There are several options not supported in FNH, i.e. Catalog, Unique, Loader and custom SQL queries (the latter is surprising, as custom SQL queries are available in component mapping, where it shouldn't). In Cascade, there are only a few options - the less used ones like refresh are not supported.

OptimisticLock is buggy. It allows to define concurrency strategies that are valid at entity level only. For collections, OptimisticLock is just a boolean flag. Running the example above (with invalid .Version() call) results in XML validation error.

There's also a problem with an interface being too fluent, again. When defining cache options, we can in fact define all its values together, what makes no sense.

m.Cache.IncludeAll().IncludeNonLazy().CustomInclude("customInclude")
.ReadOnly().NonStrictReadWrite().ReadWrite().Transactional().CustomUsage("customUsage");

Moreover, when we step into cache configuration, we have no way to go back to collection-level options - so Cache configuration needs to be at the end.

An interesting option available only in Fluent NHibernate is specifying Where condition using an expression. It seems to work only if property names are equal to column names, but anyway, it's much better than plain SQL expression in a magic string (for more complicated cases, this fallback is available, too).

What is surprising, PropertyRef is opposite - it needs to be specified by string in FNH, when mapping-by-code supports strongly-typed expression there.