Try fast search NHibernate

14 March 2010

ConfORM: “Mapping” One-To-One

In the previous post (“Mapping” Components) you saw how ConfORM can follow your domain-model changes and apply the correct mapping without addition effort.

In this post I’ll show you the same but using a one-to-one association.

Unidirectional one-to-one

The domain:

public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}

public class Address
{
public int Id { get; set; }
public string Street { get; set; }
public int CivicNumber { get; set; }
}

As you can see there is a unidirectional relation between Person and Address and I’ll map it, in ConfORM, as a one-to-one in the follow way:

public void MapDomain(ObjectRelationalMapper orm)
{
orm.TablePerClass(new[] { typeof(Person), typeof(Address) });
orm.OneToOne<Person, Address>();
}

In NHibernate a unidirectional one-to-one follows a specific mapping-pattern where the association is mapped as a many-to-one with a unique-key… in practice exactly as ConfORM map it:

<class name="Person">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
property name="Name" />
<
many-to-one name="Address" unique="true" cascade="all" />
</
class>
<
class name="Address">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
property name="Street" />
<
property name="CivicNumber" />
</
class>

If, in addition, you want apply even eager-fetch, when you get the instance by ID, you can customize the mapping in this way:

public void CustomizeMapper(Mapper mapper)
{
mapper.Class<Person>(cm => cm.ManyToOne(person => person.Address, mtom => mtom.Fetch(FetchMode.Join)));
}

to obtain this result:

<class name="Person">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
property name="Name" />
<
many-to-one name="Address" unique="true" cascade="all" fetch="join" />
</
class>
<
class name="Address">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
property name="Street" />
<
property name="CivicNumber" />
</
class>

There are two ways to customize the soft-cascade:

From where you describe your domain (the class ObjectRelationalMapper)

public void MapDomain(ObjectRelationalMapper orm)
{
orm.TablePerClass(new[] { typeof(Person), typeof(Address) });
orm.OneToOne<Person, Address>();
orm.Cascade<Person, Address>(Cascade.Persist | Cascade.Remove);
}

or from where you can customize any mapping-element (the class Mapper)

public void CustomizeMapper(Mapper mapper)
{
mapper.Class<Person>(cm => cm.ManyToOne(person => person.Address, mtom =>
{
mtom.Fetch(FetchMode.Join);
mtom.Cascade(Cascade.Persist | Cascade.Remove);
}));
}

In both cases the result mapping, for the property Address, will be:

    <many-to-one name="Address" unique="true" cascade="save-update, persist,delete" fetch="join" />

Bidirectional one-to-one (primary key association)

The domain:

public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}

public class Address
{
public int Id { get; set; }
public Person Person { get; set; }
public string Street { get; set; }
public int CivicNumber { get; set; }
}

This time I have added a property of type Person to the Address of the previous sample but the mapping does not change:

public void MapDomain(ObjectRelationalMapper orm)
{
orm.TablePerClass(new[] { typeof(Person), typeof(Address) });
orm.OneToOne<Person, Address>();
}

what will change is how ConfORM map it to NHibernate:

<class name="Person">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
property name="Name" />
<
one-to-one name="Address" cascade="all" />
</
class>
<
class name="Address">
<
id name="Id" type="Int32">
<
generator class="foreign">
<
param name="property">Person</param>
</
generator>
</
id>
<
one-to-one name="Person" constrained="true" />
<
property name="Street" />
<
property name="CivicNumber" />
</
class>

As you can see, using exactly the same mapping line (note that “line” is not plural), ConfORM can recognize the new bidirectional relation and apply the mapping-pattern with the foreign-generator and the constrain in Address and the one-to-one in Person (previously was many-to-one because unidirectional).

With the line

orm.OneToOne<Person, Address>();

I have chose Person as the master-object, you can invert the master-objet, to Address, inverting the type-arguments in this way:

orm.OneToOne<Address, Person>();

Bidirectional one-to-one (foreign key association)

If you are not completely sure that the association is and will be a one-to-one forever, instead a primary key association, in NHibernate, you should follow another mapping-pattern representing the relation, in the master-object, as a many-to-one.

The mapping in ConfORM is:

public void MapDomain(ObjectRelationalMapper orm)
{
orm.TablePerClass(new[] { typeof(Person), typeof(Address) });
orm.ManyToOne<Person, Address>();
orm.OneToOne<Address, Person>();
}

In this case, with only those two lines, ConfORM can recognize what you want do and apply a different mapping-pattern:

<class name="Person">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
property name="Name" />
<
many-to-one name="Address" unique="true" cascade="all" />
</
class>
<
class name="Address">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
one-to-one name="Person" cascade="all" property-ref="Address" />
<
property name="Street" />
<
property name="CivicNumber" />
</
class>

As you can see in Address we have the one-to-one with the property-ref and in Person we have the unique-constraint.

Conclusion

You have seen how ConfORM not only simplifies your mapping process but even how ConfORM follows your domain changes and applies correct mapping-patterns (in practice the same you saw in the previous post).

Are you ConfORM ?

I don’t need to change myself to fit the framework, it fits to me!

6 comments:

  1. What's the difference between Conform and Fluent NHibernate?

    Aren't they both achieving the same goal? Serializing the mapping model to xml documents, which NHibernate can consume?

    Basically Fabio, I'm confused!

    ReplyDelete
  2. For me a stone is exactly equals to an horse.
    For you ?

    ReplyDelete
  3. I was confused. Now I'm absolutely baffled!

    ReplyDelete
  4. People compares something to something else using their actual knowledge and their subjective vision.

    A stone is exactly equals to an horse because both are atoms.

    In my opinion FNH and ConfORM are completely different, I can find a single point to compare.

    If you are an expert in FNH you may show me how obtain the mappings, showed above, using the domains showed above.
    I'll appreciate your contribution.

    ReplyDelete
  5. Fabio, no more questions about FNH, I promise! :)

    What about NH Core? You mentioned that either ConfORM or a variant of it will be part of NH3.0. Is that still the plan? And if so, which will it be?

    Thanks again!

    ReplyDelete
  6. In core you will see "Declarative mapping".
    I'm not sure if it will be available for NH3.0

    About FNH, I don't have problems... if you avoid to ask me to write posts about comparison. If you need a comparison you can do it or ask it to somebody else.

    ReplyDelete