Fluent 1.2 Join statement not working - fluent-nhibernate

I am trying to get fluent 1.2 to work on a project that currently uses v1.1. However the existing join code no longer works. I specify a Join in the map but it never appears in the SQL.
I have a class hierarchy mapped which includes a join (see below). The application uses some legacy tables that cannot be changed. Alternatively I could rewrite the code so that the joined properties can be mapped with a reference. However this is not really valid as it produces a domain object that does not exist.
public class IndividualMap : SubclassMap<Individual>
{
public IndividualMap()
{
Map(x => x.Title);
.....
Join("UserDefFields", x =>
{
x.KeyColumn("id");
x.Map(y => y.MembershipEnquirySource, "Anl9");
....
}
);

Related

NHibernate QueryOver.List causing N+1

Having a seemingly bizzare N+1 select problem in NHibernate. I am executing a query where I'm asking for a bunch of entities where one of its linked properties is null. I don't actually need the linked property to be returned in this case by NHibernate as its only for the purpose of selecting the right data.
First Entity is a booking Window
public class BookingWindow : Entity<BookingWindow>
{
// Blah blah blah
/// <summary>
/// Gets or sets the booking order item.
/// </summary>
/// <value>
/// The booking order item.
/// </value>
public virtual BookingWindowOrderItem BookingOrderItem { get; set; }
}
And the BookingWindowOrderItem as follows
public class BookingWindowOrderItem : OrderItem
{
// Blah blah blah
public virtual BookingWindow BookingWindow { get; set; }
}
Here are the respective mappings
public BookingWindowMap()
{
this.Schema("Customer");
this.Table("BookingWindows");
this.Id(x => x.Id).GeneratedBy.Guid();
this.Component(x => x.WindowPeriod, m =>
{
m.Map(x => x.Min, "StartTime");
m.Map(x => x.Max, "EndTime");
});
this.References(window => window.BookingOrderItem).PropertyRef("BookingWindow").Column("Id").LazyLoad().Nullable().ReadOnly();
this.Map(x => x.Price);
this.References(x => x.CustomerRoom).ForeignKey("RoomId").Column("RoomId");
}
And
public BookingWindowOrderItemMap()
{
this.DiscriminatorValue(1);
this.References(x => x.BookingWindow).LazyLoad().Column("OrderItemForeignId").ForeignKey("OrderItemForeignId");
}
Now When I execute the following query I get back the correct Booking windows that don't have an order item.
Session.QueryOver<BookingWindow>().Where(w => w.CustomerRoom.Id == Guid.Parse(roomId)).Left.JoinQueryOver(bw => bw.BookingOrderItem).WhereRestrictionOn(item => item.Id).IsNull.List<BookingWindow>();
So the first query gets issued to the database like so (the order item columns are selected which is a bit annoying but the real problem comes in a minute)
SELECT this_.Id as Id2_1_, this_.Price as Price2_1_, this_.RoomId as RoomId2_1_, this_.StartTime as StartTime2_1_, this_.EndTime as EndTime2_1_, bookingwin1_.Id as Id4_0_, bookingwin1_.Price as Price4_0_, bookingwin1_.Description as Descript4_4_0_, bookingwin1_.OrderId as OrderId4_0_, bookingwin1_.OrderItemParentId as OrderIte6_4_0_, bookingwin1_.OrderItemForeignId as OrderIte7_4_0_ FROM Customer.BookingWindows this_ left outer join Payment.OrderItem bookingwin1_ on this_.Id=bookingwin1_.OrderItemForeignId and bookingwin1_.OrderItemTypeId='1' WHERE this_.RoomId = ? and bookingwin1_.Id is null
But then for each booking window returned there is an extra select for the linked order item even though I haven't asked for it or need it. This happens within the query over method so I'm not doing any kind of iterating over the returned booking windows manually.
SELECT bookingwin0_.Id as Id4_0_, bookingwin0_.Price as Price4_0_, bookingwin0_.Description as Descript4_4_0_, bookingwin0_.OrderId as OrderId4_0_, bookingwin0_.OrderItemParentId as OrderIte6_4_0_, bookingwin0_.OrderItemForeignId as OrderIte7_4_0_ FROM Payment.OrderItem bookingwin0_ WHERE bookingwin0_.OrderItemForeignId=? and bookingwin0_.OrderItemTypeId='1'
Can anyone explain to me the error I have made here. Maybe its obvious but I've struggled for a few hours and at the end of my patience :)
I see one weird part in your mapping: Using References as a one-to-one mapping style. Maybe it is intended, but this is causing that issue you have.
Firstly, as documentation says [References / many-to-one][1]
References is for creating many-to-one relationships between two
entities, and is applied on the "many side." You're referencing a
single other entity, so you use the References method. #HasMany /
one-to-many is the "other side" of the References relationship, and
gets applied on the "one side."
Other words, in the table of the BookingWindowOrderItemMap you store reference to BookingWindow. It could mean (by the DB Design), that there could be more records of OrderItem, referencing the same BookingWindow. But maybe this is what you want, and you check "uniqueness" elsewhere. The more I tried to understand your problem, I would vote for moving the reference to OrderItem in a column in the BookingWindow
Problem revealed:
To your issue. When NHibernate recieves the list of BookingWindow, the next step is to build a proxy. In this process, all valueType/string properties are set, and for references... And for references NHibernate tries to prepare the lazy load.
Simplified version is, that into each property BookingWindowOrderItem BookingOrderItem is injected a promise for an instance of the BookingWindowOrderItem, to be returned when firstly touched. In standard cases, when mapping References is used, NHibernate in this moment already loaded from the table of the BookingWindow the ReferenceId.
In your case, this ReferenceID is represented by virtual, readonly 'current item ID'. The ID which definetly exists... but the reference does not! We've selected only BookingWindows which has NULL instead of the reference.
But we do have NOT NULL Reference ID (representd by Current instance ID).
And we've used .Left.JoinQueryOver. So NHibernate is sure, that it already loaded all data in the first query... but is confused, because in his session is no OrderItem with the id equal to BookingWindow.ID/ReferenceId
That's the reason (why it tries to fix it... and does load it again)
So this is the answer, why NHibernate does "weird selects". Not a suggestion how to fix it ;) it could be another question and answer...

Mapping an extension table that might not have a row

I'm working with Fluent nHibernate on a legacy database and have a main Person table and several extension tables containing additional information about the person. These extension tables are one-to-one, meaning that a person will only have one row on the extension table and the extension table should always map back to one person.
Table: Person
Columns: PersonID, FirstName, LastName, etc.
Table: PersonLogin
Columns: PersonID (FK, unique), UserName, Password, etc.
I have my mappings defined as this (with the irrelevant properties omitted):
public PersonMap()
{
Table("Person");
Id(x => x.Id, "PersonID").Not.Nullable();
References(x => x.Login, "PersonID").LazyLoad();
}
public LoginMap()
{
Table("PersonLogin");
Id(x => x.Id, "PersonID").GeneratedBy.Foreign("Person");
References(x => x.Person, "PersonID").LazyLoad();
}
This works when I have data on both tables, but I recently learned that some of the extension tables don't have data for all Person rows. This caused me to get errors during the query. So, I added .NotFound.Ignore() to my PersonMap making it look like this:
References(x => x.Login, "PersonID").LazyLoad().NotFound.Ignore();
That caused me to get unnecessary selects from the Login table due to https://nhibernate.jira.com/browse/NH-1001 when my business layer doesn't need to project any of the extension table values. It is causing the performance to be terrible in some of my search queries.
I've scoured a lot of posts, but haven't found a rock solid answer about how to address this scenario. Below are the options I've tried:
Option One:
Create rows on the extension table to ensure there is no Person without a row on the extension table and then remove the .NotFound.Ignore().
The issue with this option is that it's a legacy database and I'm not sure where I'd need to update to ensure that a PersonLogin is inserted when a Person is inserted.
Option Two:
Remove the PersonLogin reference from my PersonMap and custom load it inside my Person class. Like this:
public class Person
{
/// <summary> Gets or sets the PersonID </summary>
public virtual int Id { get; set; }
private bool loadedLogin;
private PersonLogin login;
public virtual PersonLogin Login
{
get
{
if (!loadedLogin)
{
login = SessionManager.Session().Get<PersonLogin>(Id);
loadedLogin = true;
}
return login;
}
set
{
login = value;
loadedLogin = true;
}
}
}
The issue I'm having with it is that I can't eagerly fetch the data when performing a query to pull back a large number of Person objects and their Logins.
Option Three:
I just started playing to see if I could write a custom IEntityNotFoundDelegate to not throw the exception for these objects.
private class CustomEntityNotFoundDelegate : IEntityNotFoundDelegate
{
public void HandleEntityNotFound(string entityName, object id)
{
if (entityName == "my.namespace.PersonLogin")
{
return;
}
else
{
throw new ObjectNotFoundException(id, entityName);
}
}
}
And I added this to the config
cfg.EntityNotFoundDelegate = new CustomEntityNotFoundDelegate();
It catches my scenario and returns back now instead of throwing the error, but now when I try to project those PersonLogin properties onto my business objects, it's attempting to use the Proxy object and throws this error that I'm trying to figure out if I can handle cleanly (possibly in a IPostLoadEventListener).
System.Reflection.TargetException occurred
Message = Non-static method requires a target
I think I've got this working now by keeping the .NotFound.Ignore().
I originally stated:
That caused me to get unnecessary selects from the Login table due to https://nhibernate.jira.com/browse/NH-1001 when my business layer doesn't need to project any of the extension table values. It is causing the performance to be terrible in some of my search queries.
I was able to tweak my LINQ queries to use the IQueryOver in some instances and to improve my use of LINQ in other scenarios to project only the necessary values. This appears to have resolved the queries from pulling back the extension tables since their values were not needed in the projections.
I thought that my queries weren't projecting these extension tables, but figured out that I had a method ToKeyValuePair that I was using in the projection to concatenate the ID and a Name field together of some related properties. That method was causing the objects to load completely since LINQ wasn't able to determine that the needed fields were present without joining to the extension table.

Fluent Hibernate Mapping hitch - passing wrong column name for subclass

I finished following the summer of nhibernate screen casts and am trying to convert it to fluent, just for the sake of knowledge.
I have two classes (very simple)
public class Customer { ... }
public class PreferredCustomer : Customer { ... }
These follow table per sub class strategy and so the fluent mapping is so:
CustomerMap -
//nothing related to PreferredCustomer - the spec says not required
public class PreferredCustomerMap : SubclassMap<PreferredCustomer>
{
Map(x => x.CustomerSince);
Map(x => x.OrderDiscountRate);
}
Thats it.
My test is failing, upon inspection, its complaining that sql cant find column customer_id
this is the sql produced by hibernate:
SELECT customer0_.CustomerId as CustomerId1_0_,
customer0_.Version as Version1_0_,
customer0_.Firstname as Firstname1_0_,
customer0_.Lastname as Lastname1_0_,
customer0_1_.CustomerSince as Customer2_2_0_,
customer0_1_.OrderDiscountRate as OrderDis3_2_0_,
//here, customer0_1_.customer_id needs to be CustomerID really.
case when customer0_1_.Customer_id is not null then 1 when customer0_.CustomerId is not null then 0 end as clazz_0_
FROM [Customer] customer0_ left outer join [PreferredCustomer] customer0_1_ on customer0_.CustomerId=customer0_1_.Customerid
WHERE customer0_.Customer_Id=1
Its clearly doing that only on PreferredCustomer joined table. Cant find what needs to be done.
Any Ideas please?
edit: how do I read the xml's produced by fluent? that could be a good start.
You need a call to KeyColumn("CustomerID"); in your SubclassMaps.

Fluent NHibernate: Foreign Key violation or Null values

Hey guys, I am having some real issues with mapping using fluent nhibernate. I realise there are MANY posts both on this site and many others focusing on specific types of mapping but as of yet, I have not found a solution that solves my issue.
Here is what I have:
namespace MyProject.Models.Entites
{
public class Project
{
public virtual Guid Id {get; set;}
// A load of other properties
public virtual ProjectCatagory Catagory{get;set;}
}
}
and then the map:
namespace MyProject.DataAccess.ClassMappings
{
public class ProjectMap : ClassMap<Project>
{
public ProjectMap()
{
Id(x => x.Id);
Map(x => x.Title);
Map(x => x.Description);
Map(x => x.LastUpdated);
Map(x => x.ImageData).CustomSqlType("image");
HasOne(x => x.Catagory);
}
}
}
So as you can see, I have a project which contains a catagory property. Im not so hot on relational databases but from what I can figure, this is a many-one relationship where many Projects can have one catagory. No, projects cannot fall into more than one category.
So now we have:
namespace MyProject.Models.Entities
{
public class ProjectCatagory
{
public virtual Guid Id { get; set; }
public virtual String Name { get; set; }
}
}
and its map:
public ProjectCatagoryMap()
{
Id(x => x.Id);
Map(x => x.Name);
}
Issue is, well, it doesn't work ! I will do something similar to the following in a unit test:
Project myproject = new Project("Project Description");
// set the other properties
myProject.Catagory = new ProjectCatagory(Guid.New(), "Test Catagory");
repository.Save(myProject);
Now I have tried a number of mapping and database configurations when trying to get this to work. Currently, the Project database table has a column, "Catagory_id" (which i didnt put there, i assume NH added it as a result of the mapping) and I would LIKE it set to not allow nulls. However, when set as such, I get exceptions explaining that I cannot insert null values into the table (even though during a debug, i have checked all the properties on the Project object and they are NOT null).
Alternatively, I can allow the table to accept nulls into that column and it will simply save the Project object and totally disregard the Category property when saving, therefore, when being retrieved, tests to check if the right category has been associated with the project fails.
If i remember correctly, at one point I had the ProjectMap use:
References(x => x.Catagory).Column("Catagory_id").Cascade.All().Not.Nullable();
this changed the exception from "Cannot insert null values" to a foreign key violation.
I suspect the root of all this hassle comes from my lack of understanding of relational database set up as I have other entities in this project that do not have external dependencies which work absolutely fine with NHibernate, ruling out any coding issues I may of caused when creating the repository.
Any help greatly appreciated. Thank you.
The main issue here is a common misunderstand about the "one-to-one" relation in a relational database and the HasOne mapping in Fluent. The terms in the mapping are relational terms. (Fluent tries to "beautify" them a bit which makes it worse IMO. HasOne actually means: one-to-one.)
Take a look at the Fluent wiki:
HasOne is usually reserved for a
special case. Generally, you'd use a
References relationship in most
situations (see: I think you mean a
many-to-one).
The solution is very simple, just exchange HasOne with References (one-to-one to many-to-one in an XML mapping file). You get a foreign key in the database which references the ProjectCatagory.
A real one-to-one relation in a relational database is ideally mapped by a primary key synchronization. When two objects share the same primary key, then you don't waste space for additional foreign keys and it is ensured to be one-to-one.
To synchronize primary key, you need to hook up one's key to the others. However this works, it is not what you need here.
After playing around with all the available options for mapping. I found the answer to be similar to that suggested.
As was suspected, HasOne() was clearly wrong and References(x => x.Catagory) was part of the solution. However, I still received foreign key violation exceptions until:
References(x => x.Catagory).Column("Catagory_id").Cascade.SaveUpdate().Not.Nullable().Not.LazyLoad();
Just thought id update the thread in case someone else stumbles across this with a similar issue as just using References() did not work.
Its seems ProjectCatagory class is parent class of Project Class. So without parent class how can child class exist.
You have to use -
References(x => x.Catagory).Column("Catagory_id").Foreignkey("Id");
here Foreign Key is your ProjectCatagory table ID.

NHibernate logs and executes a query twice in a row

I am using: NHibernate, NHibernate.Linq and Fluent NHibernate on SQL Server Express 2008. I am selecting an entity using a predicate on a referenced property (many-one mapping). I have fetch=join, unique=true, lazy-load=false. I enabled the log4net log and when any such query executes it logs two identical SQL queries. Running the query returns one row, and when I attempt to use the IQueryable.Single extension method it throws the exception stating there is more than one row returned. I also tried running the query using the standard IQuery.UniqueResult method with the same result, it ends up logging and actually running the query twice, then throwing an exception stating that there were multiple rows, however running the actual query in management studio returns only one result. When I disable logging I receive the same error.
The entities and mappings are declared as follows (proper access modifiers and member type variance are implied)
class User
{
int ID;
string UserName;
}
class Client
{
int ID;
User User;
Person Person;
Address Address;
}
class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.ID);
Map(x => x.UserName);
}
}
class ClientMap : ClassMap<Client>
{
public ClientMap()
{
Id(x => x.ID);
References(x => x.User).Unique();
...
}
}
Then I invoke a queries such as the following:
ISession s = GetNHibernateSession();
...
var client = s.Linq<Client>().SingleOrDefault(x => x.User.ID = 17);
or
var client = s.Linq<Client>().Where(x => x.User.ID = 17);
or
var client = s.CreateQuery("from Client as c where c.User.ID = 17").UniqueResult<Client>();
In all cases executes two identical queries. When I enable lazy load, the client is again loaded using two queries, however upon accessing a member, such as Person, only one additional query is executed.
Is this possibly a result of Fluent generating an improper mapping? Or SQL Server Express edition not being used properly by NHibernate?
The problem was caused by another mapping I had declared. I had a class inheriting from Client which had an associated mapping. This is what caused NHibernate to query twice. I noticed this because when using Linq() it returned the subclass, not Client itself. This particular instance of inheritance and mapping was a design flaw on my part and was the root of the whole problem!
NHibernate doesn't have any trouble with SQL Express, I've used it fairly extensively. Similarily, it's unlikely Fluent NHibernate is generating invalid mappings in this simple scenario (but not unheard of).
A shot in the dark, but I believe NHibernate reserves the name Id as an identifier name, so when it sees Id in the query it knows to just look at the foreign key instead of the actual joined entity. Perhaps your naming of ID instead of Id is throwing it off?
You could try using the excellent NHibernate profiler for a more detailed view of what is happening. It comes with a 30 day trial license and while in Beta there is a discount on the full license cost