views:

538

answers:

4

I have a following mapping:

<set name="People" lazy="true" table="ProjectPeople">
  <key column="ProjectId" />
  <composite-element class="PersonRole">
    <many-to-one name="Person" column="PersonId" cascade="save-update" not-null="true" />
    <many-to-one name="Role" column="RoleId" cascade="save-update" not-null="true"  />
  </composite-element>
</set>

Now, I do not really want to have a separate class for Role in domain, I need only the Role name. However, in DB Roles should still be normalized to a separate table Role (Id, Name).

How do I map it so that People use following PersonRole class?

public class PersonRole {
    public virtual Person Person { get; set; }
    public virtual string Role { get; set; }
}

Update: added bounty, seems like a question useful not only to me.

+1  A: 

i don't think it is possible to map many-to-one to a primitive type if i were you i would add a Role class to the model

Yassir
I did this, but I do not like this.
Andrey Shchekin
I agree. The docs do says that a <many-to-one> is an association to a *persistent class*. http://nhforge.org/doc/nh/en/index.html#mapping-declaration-manytoone
John Rayner
+1  A: 

Personally I would create a Role class like Yassir

But If you want to use the structure that you have at the moment then create a view that contains the foriegn Key to your Person Table and the Role Description.

Modify the Set mapping table to point at your new view Then modify your Role mapping so that it is a property instead of the many to one mapping.

However taking this approach I think will mean that you will not be able to update your role as it is reerencing a view.

Edit: To update the role you could add <sql-insert>,<sql-update> and <sql-delete> to your mapping file so that the cascade-all will work

Nathan Fisher
I want to be able to cascade insert new Role this way, otherwise the easiest way is to use a formula column.
Andrey Shchekin
Interesting to find that you accepted this for the bounty, as you earlier said that this was *not* the approach you were after.
Abel
Yes, because I was forced by SO to accept something. This answer basically says I can not do this without hacking, which is unfortunate, but there are no points that I disagree with. I like your answer, but I think you are incorrect in several points (that I specified in comment). That wasn't an easy choice and I would have accepted both or none if either was allowed.
Andrey Shchekin
+1  A: 

You won't actually get the answer you hope for, simply because it is not possible. (N)Hibernate is an Object-Relational-Mapping framework and support three kinds of mapping strategies:

  • table per class hierarchy
  • table per subclass
  • table per concrete class

It also allows you to deviate from this by using formula or sql-insert etc, but as you've found out, these only cause you more pain in the end, are not encouraged by the Hibernate community and are bad for the maintainability of your code.

Solution?

Actually, it is very simple. You do not want to use a class for Role. I assume you mean that you do not want to expose a class of type Role and that you do not want to have to type prObject.Role.Name all the time. Just prObject.Role, which should return a string. You have several options:

  1. Use an inner class in, say, PersonRole, this class can be internal or private. Add a property Role that sets and updates a member field;
  2. Use an internal class. Add a property Role that sets and updates a member field;

Let's examine option 2:

// mapped to table Role, will not be visible to users of your DAL
// class can't be private, it's on namespace level, it can when it's an inner class
internal class Role 
{
    // typical mapping, need not be internal/protected when class is internal
    // cannot be private, because then virtual is not possible
    internal virtual int Id { get; private set; }
    internal virtual string Name { get; set; }
}

// the composite element
public class PersonRole
{
    // mapped properties public
    public virtual Person Person { get; set; }

    // mapped properties hidden
    internal virtual Role dbRole { get; set; }

    // not mapped, but convenience property in your DAL
    // for clarity, it is actually better to rename to something like RoleName
    public string Role     /* need not be virtual, but can be */
    { 
        get
        {
            return this.dbRole.Name;
        }
        set
        {
            this.dbRole.Name = value;    /* this works and triggers the cascade */
        }
    }
}

And the mapping can look as expected. Result: you have not violated the one-table-per-class rule (EDIT: asker says that he explicitly wants to violate that rule, and Hib supports it, which is correct), but you've hidden the objects from modification and access by using typical object oriented techniques. All NH features (cascade etc) still work as expected.

(N)Hibernate is all about this type of decisions: how to make a well thought-through and safe abstraction layer to your database without sacrificing clarity, brevity or maintainability or violating OO or ORM rules.


Update (after q. was closed)

Other excellent approaches I use a lot when dealing with this type of issue are:

  • Create your mappings normally (i.e., one-class-per-table, I know you don't like it, but it's for the best) and use extension methods:

     // trivial general example
     public static string GetFullName(this Person p)
     {
         return String.Format("{0} {1}", p.FirstName, p.LastName);
     }
    
    
     // gettor / settor for role.name
     public static string GetRoleName(this PersonRole pr)
     {
         return pr.Role == null ? "" : pr.Role.Name;
     }
     public static SetRoleName(this PersonRole pr, string name)
     {
         pr.Role = (pr.Role ?? new Role());
         pr.Role.Name = name;
     }
    
  • Create your mappings normally but use partial classes, which enable you to "decorate" your class any which way you like. The advantage: if you use generated mapping of your tables, you an regenerate as often as you wish. Of course, the partial classes should go in separate files so considering your wish for diminishing "bloat" this probably isn't a good scenario currently.

     public partial class PersonRole
     {
         public string Role {...}
     }
    
  • Perhaps simplest: just overload ToString() for Role, which makes it suitable for use in String.Format and friends, but of course doesn't make it assignable. By default, each entity class or POCO should have a ToString() overload anyway.

Though it is possible to do this with NHibernate directly, the q. has been closed before I had time to look at it (no ones fault, I just didn't have the time). I'll update if I find the time to do it through Hibernate HBM mapping, even though I don't agree to the approach. It is not good to wrestle with advanced concepts of Hib when the end result is less clear for other programmers and less clear overall (where did that table go? why isn't there a IDao abstraction for that table? See also NHibernate Best Practices and S#arp). However, the exercise is interesting nevertheless.

Considering the comments on "best practices": in typical situations, it shouldn't be only "one class per table", but also one IDaoXXX, one DaoConcreteXXX and one GetDaoXXX for each table, where you use class/interface hierarchy to differentiate between read-only and read/write tables. That's a minimum of four classes/lines of code per table. This is typically auto-generated but gives a very clear access layer (dao) to your data layer (dal). The data layer is best kept as spartan as possible. Nothing of these "best practices" prevent you using extension methods or partial methods for moving Role.Name into Role.

These are best general practices. It's not always possible or feasible or even necessary in certain special or typical sitations.

Abel
this is a much better solution.
Nathan Fisher
Thank you for writing a detailed answer. However, I do disagree with lot of your points. Three kinds of mapping strategies are relevant to inheritance only, and if it was the only way to do a mapping, NHibernate would not be a complete data mapper, it would be a simple active record-like mapping. For example, I can map collections of strings, and NHibernate does not need to proxy strings for that to work.
Andrey Shchekin
I also disagree about bad practices. Having class just because I have table, with no logic seems like a code smell to me, and a sign of anemic domain model (this class is basically a DTO). In your example, you have Id in Role. I do not have Id in classes so it would actually be a single-property (Name) class.I know what is technically needed to map property to a related column -- all that is needed is an "insert unless exists" by this column. It seems NHibernate does not support that, but this does not mean it is a bad practice or should not be supported.
Andrey Shchekin
I apparently misunderstood part of your question and the motivations behind it, note that something is only a bad practice in the light of a certain project domain and I can only give general advice, not knowing your project. I haven't invented NH and I haven't been the first to say that "breaking" the one-table-one-class rule is "bad", esp. in enterprise scenarios and when using S#arp or similar architectures, I can understand why you sometimes want it differently. What you're probably after is mapping to IESI `ISet` / `HashedSet`, I'll see if I can come up with an example for your use case.
Abel
PS: while I agree to *"if it is not supported it doesn't mean it is bad practice"*, the reverse is also true: *if it is supported, it is not necessarily good practice* (i.e. many-to-many, top-level collections etc).
Abel
Oops, forget my remark on `ISet` etc, that's not applicable here. I'll update my post with your and my findings.
Abel
A: 

This the biggest turn off of the whole OO purist thing. Surely the goal is to have a working application. Not somebodies version of a perfect class hierarchy. So what if you have to code "prObject.Role.Name " instead of "prObject.Role". How does this help you make a better more reliable program?

From the application design purist point of view what you want is just plain wrong. A person can have several roles, a role can usually be assigned to several people. Why go to all this trouble to enforce an unrealistic one role per person class hierachy when the undelaying data model is many roles per person?

If you really do have an "only one role per person" rule then it should be refleced in the underlying data model.

James Anderson
Well, I do have many roles per person, that is the whole point of the PersonRole class instead of having Role on Person. Now, having Role is preferrable to having Role.Name because I do not want to manage Role as an entity -- check whether it already exists, etc. I just want to set it or get it, and let NHibernate manage the storage. It is not only purism, it is also much easier when you do not have to manage something.
Andrey Shchekin