0
votes

What code need to go here:

,ConventionBuilder.HasMany.Always(x => 
          x.Key.Column( /* what code need to go here? */ )) 

Aside from making this...

public virtual Person Owner { get; set; }

...to(which is ideal only on greenfield systems):

public virtual Person Person { get; set; }

How can I prevent NHibernate from producing the extraneous Person_id in its DDL creation?

Note the Person_id was produced by NH(or FNH?) even there's already an owner_ref, and note the two references:

create table contact (contact_id int4 not null, 
                      number text, type text, owner_ref int4, 
                      Person_id int4, primary key (contact_id));
create table person (person_id int4 not null, person_name text not null, birthdate timestamp not null, primary key (person_id));
alter table contact 
      add constraint FK38B7242018FA494F 
      foreign key (owner_ref) references person;
alter table contact 
      add constraint FK38B724202B329A0D 
      foreign key (Person_id) references person;

I tried this just to make sure that it's feasible to prevent extraneous reference

,ConventionBuilder.HasMany.Always(x => x.Key.Column("owner_ref"))   

Here's NHibernate DDL Creation when I add that ConventionBuilder:

create table contact (contact_id int4 not null, number text, type text, 
                      owner_ref int4, primary key (contact_id));
create table person (person_id int4 not null, person_name text not null, birthdate timestamp not null, primary key (person_id));
alter table contact 
      add constraint FK38B7242018FA494F 
      foreign key (owner_ref) references person;

Note there are no more Person_id field, and there's only one references now, which is correct. So it prevent duplicate references, it's possible, but what I still doesn't know is how to change the KeyColumn's name of the collection(IList<Contact>) under Person in ConventionBuilder

Another way is to just change the KeyColumn in ClassMap directly, prevents duplicate reference in contact table...

HasMany(x => x.Contacts).Inverse().KeyColumn("owner_ref");

..., achieved the same SQL as above, but it's better if I can make it automatic on ConventionBuilder.

How can I tell NHibernate from producing two references? Here's the mapping code (note this: public virtual Person Owner { get; set; }. It's not Person Person { get; set; }

    public class Person
    {
        public virtual int PersonId { get; set; }

        public virtual string PersonName { get; set; }
        public virtual DateTime Birthdate { get; set; }     

        public virtual IList<Contact> Contacts { get; set; }
    }


    public class Contact
    {
        public virtual Person Owner { get; set; }

        public virtual int ContactId { get; set; }      
        public virtual string Number { get; set; }      
        public virtual string Type { get; set; }
    }





    public class PersonMap : ClassMap<Person>
    {
        public PersonMap()
        {       

            Id(x => x.PersonId);
            Map(x => x.PersonName).Not.Nullable();
            Map(x => x.Birthdate).Not.Nullable();                   
            HasMany(x => x.Contacts).Inverse(); 
        }
    }



    public class ContactMap : ClassMap<Contact>
    {
        public ContactMap()
        {                   
            References(x => x.Owner);
            Id(x => x.ContactId).GeneratedBy.Sequence("contact_contact_id_seq");
            Map(x => x.Number);
            Map(x => x.Type);                           
        }

    }

What's the right ConventionBuilder on that design pattern("_ref" suffix for child table's field referencing parent table). That potentially happen on brownfield systems too.

2
Please reduce the information, especially code, to the minimum needed to explain the problem and show what you already tried. This is quite a lot to read. You could leave away all the other properties, a whole lot of generated sql etc. You'll get more answers if one could get the point in reasonable time.Stefan Steinegger

2 Answers

0
votes

You can't make it automatic. NH can't know that you want to use the same foreign key for the two references.


EDIT theoretical solution:

You need to pass the inverse property somehow, because this is the missing part.

ConventionBuilder
  .HasMany(x => x.Contacts)
  .KeyColumn(x => GetColumnNameOf((Contact c) => c.Owner));

I don't know how to implement it to get this syntax, but this is basically the information you need to pass. If you are lucky you can obtain the Type (Contact) by the compiler from the property you are mapping.

Ideally, you get it like this:

ConventionBuilder
  .HasMany(x => x.Contacts)
  .InverseOf(c => c.Owner));
0
votes

I have found(formulated) the solution. The key thing is we must setup the joining key on both ends of the entities, i.e. we must set it up on ConventionBuilder.HasMany and ConventionBuilder.References

The ConventionBuilder.References is a solved problem. It is the ConventionBuilder.HasMany.Always(x => that we need to come up with a way of traversing the referencing object(e.g. Owner) from the .ConventionBuilder.HasMany.Always code.

We must normalize the references based on Many-To-One mappings from ConventionBuilder.HasMany. Unfortunately the references property is in protected access in ClassMap. For this, we must extend the ClassMap, and use that extended class on our mappings, so we can traverse the references

Following is the solution I coded, check the code of NormalizeReference here: http://www.ienablemuch.com/2010/12/brownfield-system-problem-on-fluent.html