2

My database layout looks like this:

Users
------------
|UserId    | <-PK
|FirstName |
|LastName  |
|MiddleName|
|Email     |
------------

Admins
------------
|UserId    | <-PK/FK to Users
------------

I Just want the ability to put in the unique ID to the Admins table to designate if that user is an Admin.

Here is my Fluent nHibernate code:

public class Users
{
    public virtual string UserId { get; set; }
    public virtual string FirstName { get; set; }
    ....
}

public class UserMappings : ClassMap<Users>
{
    public UserMappings()
    {
        Id(x => x.UserId).Column("UserId").Not.Nullable();

        Map(x => x.FirstName).Column("FirstName").Not.Nullable();
        Map(x => x.MiddleName).Column("MiddleName").Nullable();
        ....
    }
}

public class Admins
{
    public virtual Users User { get; set; }
}

public class AdminMappings : ClassMap<Admins>
{
    public AdminMappings()
    {
        Id(x => x.User).GeneratedBy.Foreign("Users");

        HasOne(x => x.User).Constrained();
    }
}

Unfortunately, when I run, I get this error:

Could not determine type for: SMAC.Users, SMAC, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null, for columns: NHibernate.Mapping.Column(User)

What I want is that if I do:

var admin;
admin.user <= instance of user to get name, email, etc...

How would I go about accomplishing this?

EDIT: I added this and I got it working though I don't know if its ideal:

public AdminMappings()
{
    Id(x => x.UserId).Column("UserId");
    HasOne(x => x.User).Constrained();
}

public UserMappings()
{
    Id(x => x.UserId).Column("UserId").Not.Nullable();
    Map(x => x.FirstName).Column("FirstName").Not.Nullable();
    Map(x => x.MiddleName).Column("MiddleName").Nullable();
    ...
    HasOne(x => x.Admin).Cascade.All();
}

public class Admins
{
    public virtual string UserId { get; set; }

    public virtual Users User { get; set; }
}

public class Users
{
    public virtual string UserId { get; set; }
    public virtual string FirstName { get; set; }
    ....
    public virtual Admins Admin { get; set; }
}

The only caveat is that I have to actually set the UserId and the User in order to save the Admin record rather then just the UserId and have it automatically pull the User record for me.

dangerisgo
  • 1,261
  • 3
  • 16
  • 28

1 Answers1

4

NHibernate can handle FK in primary keys

public class AdminMappings : ClassMap<Admins>
{
    public AdminMappings()
    {
        CompositeId().KeyReference(x => x.User, "UserId");
    }
}

Don't forget to implement equals and gethashcode on Admin appropiatly to use User.Id

Update:

To save a admin record with a given userId you can use session.Load<Users>(userId) which will not load the user record immediatly but satisfies the reference.

Firo
  • 30,626
  • 4
  • 55
  • 94
  • Keep in mind that this is not a solution, but rather, hack or workaround. Mapping ID as CompositeID instead of a simple ID, forces us to add Equals and GetHashCode to `Admins` entity, which in turn may have other consequences. – quetzalcoatl Sep 21 '18 at 13:34
  • @quetzalcoatl it is not a workaround. The OP stated that the FK is also the primary key and that should force the equals and hashcode regardless of how it is mapped. If you have 2 Admin objects for the same user you can not save it even if you have a simple UserId property. – Firo Sep 25 '18 at 09:56
  • You're mixing User and Admin. Using CompositeId() mapping in *Admin* forces to mark the user-FK as the PK causes NH to force us to add GetHashCode and Equals in the *Admin* entity, where it is almost completely an overcomplication, since the actual ID is in fact just a single integer (id of user). If we really would like to ensure "proper entity comparison" in such mapping, then the *User* entity would be required to have equals/ghc overridden, so that a PK from one Admin could match PK from the other Admin instance. However, using CompositeId in Admin has no impact on the mapping of User. – quetzalcoatl Sep 25 '18 at 10:31
  • 1
    That's why I say that this is a hack/workaround. I think that Fluent/NH should have a simpler way to map it. Sure, it's fine if the User had a complex pk - then Admin would also need it. But right now, the PK on Admin is just a single integer. There's totally no real point in forcing the Admin to carry all the complex-key hassle. Mind that I'm not saying that there is any other option. Either what OP found out himself, or what you provided. Current Fluent/NH implementation does not have any other ways to do it. For me, this is an overlooked gap in the functionality. – quetzalcoatl Sep 25 '18 at 10:42
  • OP stated the table structure is given not forced by the mapping. Admin doesn't have a simple integer id, it has a User object as id. The Equals and GetHashCode would delegate to the User because it is the primary key. Also User should have equals and gethashcode with its id. Mind you the admin.user_id is not valid without a user with that id so it is not an id on its own. – Firo Sep 26 '18 at 11:44
  • 1
    better answer: https://stackoverflow.com/a/6087236/717732 paragraph "Primary key association". No need for hackish 1-field CompositeKey. – quetzalcoatl Sep 02 '19 at 13:00
  • @quetzalcoatl i think the linked solution is more hacky because first you have redundant `Id` and `Settings.Id` which is not immediatly obvious and you have to implement equals and gethashcode, too. So how is it better than this solution? – Firo Sep 23 '19 at 14:40
  • where do you see that in that answer I linked to? There, each entity has single simple ID, and rest is configured by relation mappings, and there's no thing else to be done. Judging what you wrote, you probably refer to a solution with hacky CompositeID - and when a compositeid is used, NH requires us to override EQ and GHC. In Jakub Linhart's answer that I linked to there is no such thing, and precisely that's why I linked to it. We shouldn't use CompositeID unless the entity really has PK spanning multiple columns, and IIRC even that can be often avoided by wrapping that in a component – quetzalcoatl Sep 25 '19 at 10:51
  • @quetzalcoatl if it would be a simple ID i would be able to remove the property or mapping `HasOne(x => x.Setting).Constrained();`. But it won't work. Also when creating an instance of Student it is not obvious that you have to set a valid settings object and that you have to make sure to set `Id == Settings.Id` otherwise you have a broken model. EQ and GHC also has to be overridden otherwise it will be wrong for unsaved instances. Try `Equals(new Student {Setting=new Setting() }, new Student { Setting = new Setting() })` in both solutions and see the difference. – Firo Oct 21 '19 at 08:52
  • If access the details of the 'Setting' entity is not needed, it's totally fine to map that field as plain integer and pass around an opaque unanalyzed int obtained from elsewhere. Even if it is mapped as an entity ref (because it made accessing related data easier?), still, for NH, the PK is an simple integer and not a complex key, so neither EQ or GHC overloads are not _required_. – quetzalcoatl Oct 26 '19 at 12:15
  • As for the `try` example you provided, it's just wrong. It seems like either you are using stateless sessions, or mixing objects from different sessions, or didn't take *proper* care of bookkeeping of transients. NH uses identity-map pattern and goes to a great lengths to ensure that you get the same entity object instances when asking for entities of the same IDs. NH gives that to you and expects you to follow that pattern. You should not give NH multiple object instances and expect it will guess how to properly merge them into 1 db record. Your `try` example compares 4 new entities, not 2. – quetzalcoatl Oct 26 '19 at 12:31