0

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.

Andrey Shchekin
  • 21,101
  • 19
  • 94
  • 162
  • 1
    I think by not creating class Role, you have a discrepancy between your java object hierarchy and your database model. Your DB has Role(Id, Name) - no two Role records should have the same Name; when a Role Name is updated, all PersonRoles should reflect the change. All PersonRoles with the "USER" role should refer to the same Role object. Using a String in your Java code, you are providing client code a means to assign arbitrary values, not just existing table values. You could cause duplicate name entries in the table. You can work around Hibernate to do this anyway, but IMHO it isn't wise. – RMorrisey Nov 03 '09 at 17:43
  • To _have a discrepancy between your java object hierarchy and your database model_ is the whole point of having an advanced mapping framework for me -- so I can have objects modeled by object rules, and db modeled by db rules. Right now I do want to allow clients to produce arbitrary values, and want these values to be added to the DB or reused if there is already same one here (solving the duplicates problem). – Andrey Shchekin Nov 03 '09 at 19:36
  • Seems that you are actually after the enumerator-pattern, but then without a declared `enum` type. This really is easiest if you allow Role to be a class with an associated DAO for Update/Add etc and a unique constraint. I hinted at the practices of using `IDaoXXX` in an update of my answer. Note that "code bloat" is not always "bad bloat": to make programming this kind of business rules (re-use if exists, but updateable) far easier. This type of business rules should *never* go into the mapping (entity) layer (apart from the constraints) but should go in the access (dao) layer. – Abel Nov 04 '09 at 12:18

4 Answers4

2

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
  • 56,041
  • 24
  • 146
  • 247
  • this is a much better solution. – Nathan Fisher Nov 03 '09 at 02:41
  • 1
    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 Nov 03 '09 at 11:09
  • 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 Nov 03 '09 at 11:18
  • 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 Nov 03 '09 at 12:07
  • 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 Nov 03 '09 at 12:12
  • Oops, forget my remark on `ISet` etc, that's not applicable here. I'll update my post with your and my findings. – Abel Nov 03 '09 at 12:19
1

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

Hannoun Yassir
  • 20,583
  • 23
  • 77
  • 112
1

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
  • 7,961
  • 3
  • 47
  • 68
  • I want to be able to cascade insert new Role this way, otherwise the easiest way is to use a formula column. – Andrey Shchekin Oct 30 '09 at 10:10
  • Interesting to find that you accepted this for the bounty, as you earlier said that this was *not* the approach you were after. – Abel Nov 04 '09 at 12:11
  • 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 Nov 04 '09 at 18:54
0

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
  • 27,109
  • 7
  • 50
  • 78
  • 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 Nov 04 '09 at 08:38