I have two NHibernate entities with a one-to-one relationship. Let's call them Dog and Owner.
public class Dog
{
public virtual int Id { get;set; }
public virtual Owner Owner { get; set; }
}
public class Owner
{
public virtual int Id { get; set; }
public virtual Dog Dog { get; set; }
}
And Dog may have one or zero owners. The Fluent/NHibernate mappings are as so:
public class DogMap : ClassMap<Dog>
{
public DogMap()
{
Table("Dogs");
Id(x => x.Id);
HasOne( x=> x.Owner)
.Fetch.Join()
.Not.LazyLoad();
}
}
public class OwnerMap : ClassMap<Owner>
{
public OwnerMap()
{
Table("Owners");
// Owners share the same primary-key as dogs
Id(x => x.Id).GeneratedBy.Foreign("Dog");
References( x => x.Dog)
.Unique()
.Not.LazyLoad();
}
}
Now I simply wish to select Dogs which don't have an Owner.
I assumed the query would be:
Owner owner = null;
var ownerlessDogs = session
.QueryOver<Dog>()
.Left.JoinAlias(x => x.Owner, () => owner)
// Restrict on alias
.WhereRestrictionOn(() => owner).IsNull
.List();
But as you may have guessed, this does not work. It throws 'Object reference not set to ...'.
If I try,
var ownerlessDogs = session
.QueryOver<Dog>()
.Left.JoinAlias(x => x.Owner, () => owner)
// Restrict on property of root object
.WhereRestrictionOn(x => x.Owner).IsNull
.List();
It generates basically generates
SELECT {{relevant columns}}
FROM Dogs dogs
LEFT OUTER JOIN OWNERS owners
WHERE dogs.Id IS NULL
Almost right, but it is filtering on the primary-key of dogs, rather than the dogs foreign-key on owners.
With some help from an old post in the Hibernate forum I found a solution:
public class DogMap : ClassMap<Dog> {
public DogMap() {
Table("Dogs");
Id(x => x.Id);
HasOne( x=> x.Owner)
.Fetch.Join();
}
}
public class OwnerMap : ClassMap<Owner> {
public OwnerMap() {
Table("Owners");
//owners share the same primary-key as dogs
Id(x => x.Id).GeneratedBy.Foreign("Dog");
//both sides should be mapped as HasOne
HasOne( x => x.Dog)
.Constrained()
.Fetch.Join();
}
}
And the working query is
var ownerlessDogs = session
.QueryOver<Dog>()
.Left.JoinAlias(x => x.Owner, () => owner)
//this is the trick, restrict on Id
.WhereRestrictionOn( x => x.Owner.Id ).IsNull
.List();
Related
I have the following classes defined:
And these tables in my database:
My fluent NHibernate mappings are:
public class BusinessUnitMap : ClassMap<BusinessUnit>
{
public BusinessUnitMap()
{
Table("BusinessUnits");
Id(x => x.Id);
Map(x => x.Code);
Map(x => x.Name);
Map(x => x.ParentId);
Map(x => x.Type).Column("Type").CustomType<BusinessUnitType>();
}
}
public class CompanyMap : SubclassMap<Company>
{
public CompanyMap()
{
Table("CompanyData");
KeyColumn("BusinessUnitID");
Map(x => x.Something);
}
}
public class FranchiseeMap : SubclassMap<Franchisee>
{
public FranchiseeMap()
{
Table("FranchiseeData");
KeyColumn("BusinessUnitID");
Map(x => x.SomethingDifferent);
}
}
public class StoreMap : SubclassMap<Store>
{
public StoreMap()
{
Table("StoreData");
KeyColumn("BusinessUnitID");
Map(x => x.SomethingElse);
}
}
Question #1
As far as I can tell, my code and database are setup the same as every example I've been able to find. According to those articles, NHibernate is supposed to be smart enough to determine what subclass to instantiate when I query for a particular subclass. But, when I execute the following statement:
var result = Session.QueryOver<BusinessUnit>()
.Where(x => x.Code == "Acme")
.SingleOrDefault();
an exception is thrown because it can't create an instance of the abstract BusinessUnit class. The only way I can get this to work is to specify Company as the type argument for QueryOver.
I've confirmed that using a discriminator breaks since NHibernate is looking for all of the columns to exist in a single table. Without it, though, I struggle to see how NHibernate would know what type to instantiate.
What am I doing wrong? Is the problem in my mappings, the way I'm querying, ...?
Question #2
When I change the query to something like this:
public T WithCode<T>(String code)
where T : BusinessUnit
{
var result = Session.QueryOver<T>()
.Where(x => x.Code == code)
.SingleOrDefault();
return result;
}
I get an exception indicating that the UPDATE statement conflicts with a foreign key constraint. Update statement!!!! Clearly something is still not right. How can a QueryOver call result in an UPDATE statement? What am I missing?
it looks like your data is not consistent. It might be better to use discrimnator mapping with optional. If you dont really need a BusinessUnitType property in code then just delete everything around the property Type
public enum BusinessUnitType
{
Company,
Franchisee
}
public abstract class BusinessUnit
{
public virtual int Id { get; set; }
public virtual string Code { get; set; }
public virtual string Name { get; set; }
public virtual BusinessUnit Parent { get; set; }
public abstract BusinessUnitType Type { get; }
}
public class Company : BusinessUnit
{
public virtual string Something { get; set; }
public override BusinessUnitType Type { get { return BusinessUnitType.Company; } }
}
public class Franchisee : BusinessUnit
{
public virtual string SomethingDifferent { get; set; }
public override BusinessUnitType Type { get { return BusinessUnitType.Franchisee; } }
}
public class BusinessUnitMap : ClassMap<BusinessUnit>
{
public BusinessUnitMap()
{
Table("BusinessUnits");
Id(x => x.Id);
Map(x => x.Code);
Map(x => x.Name);
References(x => x.Parent);
DiscriminateSubClassesOnColumn("Type");
Map(x => x.Type, "Type")
.Access.None()
.CustomType<BusinessUnitType>().ReadOnly();
}
}
public class CompanyMap : SubclassMap<StrangeTablePerSubclass.Company>
{
public CompanyMap()
{
DiscriminatorValue((int)new Company().Type);
Join("CompanyData", join =>
{
join.KeyColumn("BusinessUnitID");
join.Optional();
join.Map(x => x.Something);
});
}
}
public class FranchiseeMap : SubclassMap<Franchisee>
{
public FranchiseeMap()
{
DiscriminatorValue((int)new Franchisee().Type);
Join("FranchiseeData", join =>
{
join.KeyColumn("BusinessUnitID");
join.Optional();
join.Map(x => x.SomethingDifferent);
});
}
}
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
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.
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.
I have couple of classes and want to map them correctly to database:
public class A
{
public virtual Guid Id { get; private set; }
public virtual ComponentClass Component { get; set; }
}
public class ComponentClass
{
public virtual IList<B> Elements { get;set; }
}
public class B
{
public virtual Guid Id { get; private set; }
public virtual DateTime Time { get; set; }
}
I map them using fluent mappings like that:
public class AMap : ClassMap<A>
{
public A() {
Id(x => x.Id);
Component(x => x.Component,
c => c.HasMany(x => x.Elements).Inverse().Cascade.All());
}
}
public class BMap : ClassMap<B>
{
public B() {
Id(x => x.Id);
Map(x => x.Time);
}
}
When I save my entity, I have class A mapped to one table and class B to another as expected.
But I have nulls in Component_id column.
Can you tell me what am I missing here?
I believe Components are supposed to be in the same table , as clearly stated in Ayende's blog post, as they serve only to make the data better represented as an object model. Be sure to read through his blog, it's probably one of the best nHibernate resources out there.
Ok, I've resolved my problem - I can use Id of my "parent" class. So the component mapping will become:
public class AMap : ClassMap<A>
{
public A() {
Id(x => x.Id);
Component(x => x.Component,
c => c.HasMany(x => x.Elements).Cascade.All().Column("Id"));
}
}
So obvious as I look at it now ... but It took me an hour.
If you have a one-to-many association direct to a collection of components (ie. without the ComponentClass wrapper as per the question) then you can map it directly:
HasMany(x => x.Elements)
.AsSet()
.Table("ElementTable")
.KeyColumn("KeyColumn")
.Cascade.All()
.Component(x =>
{
x.Map(c => c.Id);
x.Map(c => c.Time);
})
.LazyLoad();