Map One-To-One Relationship Doesn't Allow Inserting - nhibernate

I'm trying to setup a one-to-one mapping from my Users to the UserDetails table. Say I have the following tables in my database:
Users:
- UserID (PK, Identity)
- UserName
- Password
UsersDetails:
- UserID (PK, FK)
- FirstName
- LastName
I have created the following poco classes:
public class User {
public virtual int UserID { get; set; }
public virtual string UserName { get; set; }
public virtual string Password { get; set; }
public virtual UserDetails Details { get; set; }
}
public class UserDetails {
public virtual int UserID { get; set; }
public virtual User User { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public UserDetails() {
}
public UserDetails(User user) {
User = user;
}
}
Which are fluently mapped (please note the xml mapping is very similar and if all you know is the xml mapping then I would still appreciate you guidance):
public class UserMap : ClassMap<User> {
public UserMap() {
Table("Users");
Id(x => x.UserID);
Map(x => x.UserName);
Map(x => x.Password);
HasOne(x => x.Details)
.Constrained()
.Cascade.All();
}
}
public class UserDetailsMap : ClassMap<UserDetails> {
public UserDetailsMap() {
Table("UsersDetails");
Id(x => x.UserID)
.GeneratedBy.Foreign("User");
HasOne(x => x.User)
.Constrained();
Map(x => x.FirstName);
Map(x => x.LastName);
}
}
Everything displays correctly but if I say:
var user = new User() { UserName = "Test", Password = "Test" };
user.Details = new UserDetails(user) { FirstName = "Test", LastName = "Test" };
session.Save(user);
I get the error:
"NHibernate.Id.IdentifierGenerationException: null id generated for: UserDetails."
I'd really appreciate it if someone could show me what I've done wrong. Thanks
Edit: Courtesy of Jamie Ide's suggestion. I have changed my User mapping to:
public class UserMap : ClassMap<User> {
public UserMap() {
Table("Users");
Id(x => x.UserID);
Map(x => x.UserName);
Map(x => x.Password);
References(x => x.Details, "UserID")
.Class<UserDetails>()
.Unique();
}
}
But now when i insert a user i get the error:
"System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection."
If i add Cascade.All() on to my Reference i receive the original error i was getting about the null id generated.

I think Constrained should only be specified in the UserDetailsMap, not in the UserMap. Try:
public class UserMap : ClassMap<User> {
public UserMap() {
Table("Users");
Id(x => x.UserID);
Map(x => x.UserName);
Map(x => x.Password);
HasOne(x => x.Details)
.Cascade.All();
}
}
EDIT:
See this and this. It appears that you have to map the relationship as many-to-one from the User side and one-to-one constrained from the UserDetails side to get lazy loading working with one-to-one. I don't know if this is different in NH3.

I am not sure the way you are mapping the ID property is correct. shouldnt it just be an autoincremented id and your mapping should be as follows:
Id(x=>x.UserId);
I dont know the idea behind using Foreign and how it fits into things here.. can you please illustrate the logic behind that?

I think you need to have the foreign key identity mapping in your User mapping instead of the UserDetails and the native one in the UserDetails. I have been unable to find a reference for it, though.

http://gorbach.wordpress.com/2010/09/24/fluent-onetoone/
HasOne(x => x.Details).Cascade.All().ForeignKey("UserDetails");
HasOne(x => x.User).Constrained();
var user = new User() { UserName = "Test", Password = "Test" };
user.Details = new UserDetails(user) { FirstName = "Test", LastName = "Test" };
user.Details.User = user;
session.Save(user);

After further research it appears that this won't work in version 2.0. I am now in a position to upgrade to version 3.0 where i believe this is now possible.

Related

"Tried to add property 'Id' when already added."-Error when implementing own class twice

I would like to have a mapped class named "Message". This class should include an unique id, the title, the text, and information about the sender and the receiver. I need their User-ID an their name, so I've created another class named "User". This class include these two properties (later I'll create some methods for this class and use it in different classes, so I can not use onyl the class "Message").
This is my code
public class User
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
public class Message
{
public virtual Guid Id { get; set; }
public User sender;
public User receiver;
public virtual string subject { get; set; }
public virtual string text { get; set; }
}
public class MessageMap : ClassMap<Message>, IMappedEntity
{
public MessageMap()
{
Id(x => x.Id, "MessageId");
Map(x => x.sender.Id, "SenderId");
Map(x => x.receiver.Id, "ReceiverId");
Map(x => x.subject);
Map(x => x.text);
}
}
As you can see, I want to save only the User-ID of the sender and receiver, not their names. Because x.Id, x.sender.Id and x.receiver.Id have the property "Id", I wrote down a spezific name for them in the database.
But if I try to load the site, this error appears: Tried to add property 'Id' when already added., even if their is no more property named "Id" after I definited the Name for the columns...
Could you give me a hint what I'm doing wrong?
I finally found an other solution: I changed the mapping of my Message-Map to this:
public class MessageMap : ClassMap<Message>, IMappedEntity
{
public MessageMap()
{
Id(x => x.Id);
Component(
x => x.sender,
userId =>
{
userId.Map(x => x.Id, "senderId");
}
);
Component(
x => x.receiver,
userId =>
{
userId.Map(x => x.Id, "receiverId");
}
);
Map(x => x.subject);
Map(x => x.text);
}
Now, I do not have to map the User-Class, so I don't have a table only with my Userids.
I'm not too familar with using nHibernate, so I'm not shure wether this is the best way to solve the problem, but in my eyes this fits a bit more to my problem then the solution presented by Chev (but I'm very grateful that you have answered me!)
Change the MessageMap as follows.
public class MessageMap : ClassMap<Message>, IMappedEntity
{
public MessageMap()
{
Id(x => x.Id);
References(x => x.Sender);
References(x => x.Receiver);
Map(x => x.subject);
Map(x => x.text);
}
}

OneToOne mapping by code nhibernate 3.2

I'm trying to upgrade a project and use the build in code mapper.
I have 2 entities:
public class Person {
public virtual int Id { get; set; }
public virtual User User { get; set; }
}
public class User {
public virtual int Id { get; set; }
public virtual User Person { get; set; }
}
The database structure is like this:
table: users, fields: id
table: people, fields: id, userId
With FluentNHibernate i could map it like this:
public class UserMap : ClassMap<User> {
public UserMap() {
Id(x => x.Id).GeneratedBy.Identity();
HasOne(x => x.Person).PropertyRef("User").Not.LazyLoad();
}
}
public class PersonMap : ClassMap<Person> {
public PersonMap() {
Id(x => x.Id).GeneratedBy.Identity();
References(x => x.User).Column("UserId").Not.Nullable().Not.LazyLoad();
}
}
But is can't get it working with NH 3.2 build in code mapper.
This is what i have done so far.
OneToOne(x=>x.Person, m => {
m.PropertyReference(typeof(Person).GetProperty("User"));
});
OneToOne(x=>x.User, m => {});
Now the relation is mapped on User.Id and Person.Id but personid and userid can be different.
It's also possible that a user has no person.
from Users user0_ left outer join People person1_ on user0_.Id=person1_.Id
I think i have to specify that Person -> User is mapped with the UserId column but how?.
Since you were using References() in Fluent, you need to convert that to ManyToOne():
public class UserMap : ClassMapping<User>
{
public UserMap()
{
Id(x => x.Id, x => x.Generator(Generators.Identity));
OneToOne(x => x.Person,
x => x.PropertyReference(typeof(Person).GetProperty("User")));
}
}
public class PersonMap : ClassMapping<Person>
{
public PersonMap()
{
Id(x => x.Id, x => x.Generator(Generators.Identity));
ManyToOne(x => x.User,
x => x => { x.Column("UserId"); x.NotNullable(true); });
}
}
Two notes:
The type of User.Person should be Person, not user (that's probably just a bad edit)
.Not.LazyLoad() is almost always a bad idea. See NHibernate is lazy, just live with it

Getting records back from many to many fluent nhibernate?

I made a many to many relationship by following the fluent nhibernate Getting started tutorial .
(source: fluentnhibernate.org)
Now I am not sure how to retrieve data. For instance what happens if I want to get all the products a store carries.
So I would need to use the storeId on the products table. Yet there is no storeId in the products table and I don't have a class that actually contains mapping or properties for StoreProduct.
So I can't go
session.Query<StoreProduct>().Where(x => x.StoreId == "1").toList();
So do I need to do a join on Store and Products and then do a query on them?
Edit
Here is a watered down version of what I have.
public class Student
{
public virtual Guid StudentId { get; private set; }
public virtual IList<Course> Courses { get; set; }
public virtual IList<Permission> Permissions{get; set;}
public Student()
{
Courses = new List<Course>();
Permissions = new List<Permission>();
}
public class StudentMap : ClassMap<Student>
{
public StudentMap()
{
Table("Students");
Id(x => x.StudentId).Column("StudentId");
HasManyToMany(x => x.Permissions).Table("PermissionLevel");
HasManyToMany(x => x.Courses).Table("PermissionLevel");
}
}
public class CourseMap : ClassMap<Course>
{
public CourseMap()
{
Table("Courses");
Id(x => x.CourseId).Column("CourseId");
HasManyToMany(x => x.Permissions ).Table("PermissionLevel");
HasManyToMany(x => x.Students).Table("PermissionLevel");
}
}
public class Course
{
public virtual int CourseId { get; private set; }
public virtual IList<Permission> Permissions { get; set; }
public virtual IList<Student> Students { get; set; }
public Course()
{
Permissions = new List<Permission>();
Students = new List<Student>();
}
}
public class PermissionMap : ClassMap<Permission>
{
public PermissionMap()
{
Table("Permissions");
Id(x => x.PermissionId).Column("PermissionId");
HasManyToMany(x => x.Students).Table("PermissionLevel");
}
}
public class Permission
{
public virtual int PermissionId { get; private set; }
public virtual IList<Student> Students {get; set;}
public Permission()
{
Students = new List<Student>();
}
}
var a = session.Query<Student>().Where(x => x.Email == email).FirstOrDefault();
var b = session.Get<Student>(a.StudentId).Courses;
error what I get when I look into b.
could not initialize a collection:
[Student.Courses#757f27a2-e997-44f8-b2c2-6c0fd6ee2c2f][SQL:
SELECT courses0_.Student_id as
Student3_1_, courses0_.Course_id as
Course1_1_, course1_.CourseId as
CourseId2_0_, course1_.Prefix as
Prefix2_0_, course1_.BackgroundColor
as Backgrou3_2_0_ FROM PermissionLevel
courses0_ left outer join Courses
course1_ on
courses0_.Course_id=course1_.CourseId
WHERE courses0_.Student_id=?]"
No, you don't. StoreProduct is special table that hold many-to-many relations between Store and Products. NHibernate able to populate Store's collection of products using this table automatically. It should be described in mapping. You should query just like this:
var storeProducts = session.Get<Store>(1).Products;
Here's mapping for Store:
public class StoreMap : ClassMap<Store>
{
public StoreMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.Staff)
.Inverse()
.Cascade.All();
HasManyToMany(x => x.Products)
.Cascade.All()
.Table("StoreProduct");
}
}
Notice line HasManyToMany(x => x.Products) and .Table("StoreProduct") they tell NHibernate to use table StoreProduct as source for many-to-many relation store objects in collection Products.
You have wrong mappings for Student:
HasManyToMany(x => x.Permissions).Table("PermissionLevel");
HasManyToMany(x => x.Courses).Table("PermissionLevel");
It should be following:
HasManyToMany(x => x.Courses).Table("StudentCourses");
And you Student class is incomplete.

FluentNhibernate HasManytoMany Relation - It doesnt add into the link table

Using fluentnhibernate i am having a problem with the link table insertion.
Here is my entities
public partial class Item
{
public virtual int Id
{
get;
set;
}
public virtual string Description
{
get;
set;
}
public virtual IList<Category> Categories
{
get;
set;
}
}
public partial class Category
{
public virtual int Id
{
get;
set;
}
public virtual string Name
{
get;
set;
}
public virtual string Description
{
get;
set;
}
public virtual IList<Item> Items
{
get;
set;
}
}
Here is my mappings.
public class ItemMapping : ClassMap<Item>
{
public ItemMapping()
{
Table("Item");
Schema("dbo");
Id(x => x.Id);
Map(x => x.Description);
HasManyToMany(x => x.Categories)
.ChildKeyColumn("Item_id")
.ParentKeyColumn("Category_id")
.Table("CategoriesToItems")
.AsSet();
}
}
public class CategoryMapping : ClassMap<Category>
{
public CategoryMapping()
{
Table("Category");
Schema("dbo");
Id(x => x.Id);
Map(x => x.Description);
Map(x => x.Name);
HasManyToMany(x => x.Items)
.ChildKeyColumn("Category_id")
.ParentKeyColumn("Item_id")
.Table("CategoriesToItems")
.AsSet();
}
}
Here is how i add it to collection in my mvc page
var category = CategoryTask.Query(x => x.Id == post.Category).FirstOrDefault();
var item = new Item
{
Categories = new List<Category> { category },
Tags = tags
};
ItemTasks.Save(item);
My question is why it doesnt add the relations in my link table "CategoriesToItems". The table is already in the database with Category_Id (FK, int, not null) and Item_Id (FK, int, not null).
Where is the problem? why it doesnt add it to relation table?
It's hard to say what's really wrong when we can't see what your ItemTasks.Save does under the covers. Are you wrapping your save in a transaction? If not, you should be.
You should call Session.Flush() just before the transaction.Commit() as well.
I am not certain if the problem has been solved, but it looks similar to my problem (fluentnhibernate hasmanytomany same identifier exception).
Also, it looks like your parent and child key columns are backward.

NHibernate Mapping Question

I have tried various approaches to mapping the following structure, but I finally admit that after a day of not getting very far, I need some help.
So the question is, how would you guys go about mapping something like this. The schema is not fixed at this point.
public abstract class BaseObject
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual string Prefix { get; set; }
public virtual string Suffix { get; set; }
public virtual BaseObject Parent { get; set; }
}
public class Room : BaseObject
{
public virtual int AreaId { get; set; }
}
public class Item : BaseObject
{
public virtual string Owner { get; set; }
public virtual IList<ItemAttribute> Attributes { get; set; }
public virtual int ItemTypeId { get; set; }
}
public class Potion : Item
{
public virtual int AmountLeft { get; set; }
}
Your input is very much appreciated.
This allows you to have it all in one table... doing this from memory, so syntax might not be exact.
public class ItemMap : ClassMap<BaseObject>
{
...
WithTable("objects");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name);
Map(x => x.Description);
...
DiscriminateSubClassesOnColumn("Type")
.SubClass<Room>("Room", x =>
{
x.Map(r => r.AreaId);
})
.SubClass<Item>("Item", c =>
{
i.Map(x => x.Owner);
i.References(x => x.Account).LazyLoad();
HasManyToMany(x => x.Attributes)
.WithParentKeyColumn("ItemId")
.WithChildKeyColumn("AttributeId")
.WithTableName("ItemAttributes")
.LazyLoad();
});
.SubClass<Potion>("Potion", x =>
{
x.Map(p => p.AmountLeft);
})
I would probably have a table for each class - Room, Item, Potion and then do fairly standard mappings for each.
I'd like to note that in my own experiences, it is a bad idea to name your Id field in your business objects "Id"
Here's a sample with Item, assoming some data names for your table.
public class ItemMap : ClassMap<Item>
{
public ItemMap()
{
WithTable("Items");
Id(x => x.Id, "ItemId").GeneratedBy.Identity();
Map(x => x.Name);
Map(x => x.Description);
Map(x => x.Prefix);
Map(x => x.Suffix);
Map(x => x.Owner);
Map(x => x.ItemTypeId);
References<Item>(x => x.Parent, "ParentItemId");
HasManyToMany(x => x.Attributes)
.WithParentKeyColumn("ItemId")
.WithChildKeyColumn("AttributeId")
.WithTableName("ItemAttributes")
.LazyLoad();
}
}
This is more than likely not be perfect - as I'm not sure how the mapping will work with the abstract parent.