Monday, January 30, 2012

Mapping-by-Code - OneToOne

Moving on with relation types in NHibernate - we haven't talked about <one-to-one> yet. The name suggests that it's designed for 1:1 relationships and this is not quite correct. 1:1 relationships can be mapped simply using many-to-one with unique constraint. One-to-one is designed specifically for making that many-to-one relationship with unique constraint bidirectional.

This kind of relation is mapped using many-to-one on the side that owns the relationship (in terms of having foreign key column in the database) and one-to-one on the second side. Note that at one-to-one side there's no column at the database level - the relationship from "one" side is "virtual", maintained only by NHibernate.

In mapping-by-code we're mapping it using OneToOne method. It has two parameters. The first one is the lambda pointing to the mapped property, as always. The second one are the options.

OneToOne(x => x.User, m =>
{
m.Cascade(Cascade.Persist);
m.Constrained(true);
m.Lazy(LazyRelation.Proxy); // or .NoProxy, .NoLazy
m.PropertyReference(typeof(User).GetPropertyOrFieldMatchingName("OtherSideProperty"));
m.OptimisticLock(true);
m.Formula("arbitrary SQL expression");
m.Access(Accessor.Field);
});

Two things to note here. The first one is the lack of possibility to choose the fetch mode and foreign key name. The second one is the cumbersome construct needed to map property-ref option - the method is called PropertyReference instead of standard PropertyRef and its parameter is MethodInfo-typed instead of standard lambda expression or string at least. It looks like the feature went to production before it was finished.

Fluent NHibernate's equivalent

Fluent NHibernate's name for one-to-one is HasOne. I think this is the most often misused method in FNH as it looks like a natural sibling for HasMany and HasManyToMany and it seems to be a good choice for many-to-one mapping (which turns out to be mapped by References method). A bit messy, isn't it?

Here's how to use it:

HasOne(x => x.User)
.Cascade.SaveUpdate()
.Constrained()
.LazyLoad(Laziness.Proxy) // or .NoProxy, .False
.PropertyRef(x => x.OtherSideProperty)
.Access.Field()
.Class<CustomType>()
.Fetch.Join() // or .Select(), .Subselect()
.ForeignKey("foreignKeyName");

Apart for the name issue, it's a bit better than in mapping-by-code this time. The only option not available here is formula.

6 comments:

  1. So, How can I create a Formula with NH Mapping-by-Code?

    ReplyDelete
    Replies
    1. Formula is just a plain SQL string that will be used to fetch the relation instead of fetching key column value, i.e. you can have subselect there:

      m.Formula("(select some_value from some_table)");

      Delete
    2. Sorry, what I wanted to ask is how can I declare a Formula property with NH Mapping by code... With Fluent NH I can simple write :

      Map(x => x.Property).Formula("MY SQL")

      How can I do that ?

      Thanks !

      Delete
    3. Property(x => x.Property, m => m.Formula("MY SQL"));

      See the post about mapping simple Property

      Delete
  2. In order to define one to one mapping, we've to define One to One mapping relation in both tables?

    ReplyDelete
  3. I hope you're still around.

    I have scoured the net and I can't find an answer to this question.

    I have no trouble setting up a many-to-one (Tag) and an inverse one-to-one (Asset).

    I can create a Tag and set an Asset on it and save and it just works. I can clear that Asset from the Tag, save the Tag, and it works.

    I can create an Asset and put a Tag on it. In order for the Tag to get the Asset ID saved onto it, in the Tag setter on the Asset I set the Tag on the Asset. When the tag is saved NHibernate is then picking up the change to the tag and saving it. It will not work if I don't do this assignment.

    I can not however clear a Tag from the Asset and have it save properly.

    So my question, is it possible to set a property to null on the one-to-one side and have it properly save to the database without handling it somehow in an interceptor/event listener layer or at a repository level by tracking property changes?

    public FTagMap()
    {
    ManyToOne(x => x.Asset, map => {
    map.Column("AssetId");
    map.Lazy(LazyRelation.NoLazy);
    map.Unique(true);
    });
    Property(x => x.TagNo);
    }

    public AssetMap()
    {
    Property(x => x.Alias);
    OneToOne(x => x.Tag, m =>
    {
    m.Cascade(Cascade.Persist);
    m.Lazy(LazyRelation.NoLazy);
    m.PropertyReference(typeof(FTag).GetPropertyOrFieldMatchingName(FTag.ASSET_PROP));
    });
    }

    Thanks,
    Joseph Bagadonitz



    ReplyDelete