How to map 3 references using the same column in NHibernate? - nhibernate

I have following Ticket class:
public class Ticket {
public virtual int Id { get; set; }
public virtual Type Type { get; set; }
public virtual Status Status { get; set; }
public virtual State State { get; set; }
}
that maps to a table in legacy database with the following mapping:
public TicketMap()
{
Table("TICKET");
LazyLoad();
Id(x => x.Id)
.GeneratedBy.TriggerIdentity()
.Column("ID")
.Access.Property()
.Not.Nullable();
References(x => x.Type)
.Class<Type>()
.Cascade.None()
.LazyLoad()
.Columns("TYPE_ID");
References(x => x.Status)
.Class<Status>()
.Cascade.None()
.Columns("STATUS_ID", "TYPE_ID");
References(x => x.State)
.Class<State>()
.Cascade.None()
.LazyLoad()
.Columns("STATE_ID", "TYPE_ID");
}
The problem is that when I try to save a Ticket with State and Status NHibernate doesn't know which TYPE_ID to use and fails to save the entity.
Both State and Status classes have composite keys (which I know is a bad practice but, as mentioned above, it's legacy database and I'm not able to create surrogate keys).
I there a way to make this reference work?

One possible way is to make these properties read-only. In xml configuration (xml mapping is world I know) it would look like this (the same for other two ref types: Type, State):
<many-to-one name="Status" insert="false" update="false" >
<column name="STATUS_ID" />
<column name="TYPE_ID" />
</many-to-one>
...
And then create protected artificial properties
<property name="StatusId" column="STATUS_ID" insert="true" update="true" />
<property name="StateId" column="STATE_ID" /><!-- above insert and update attr are redundant -->
<property name="TypeId" column="TYPE_ID" />
These properties should be declared as protected on the Ticket entity. With some internal logic, they can be set to proper values based on selected State, Status or Type.
What we get, is full access to query engine, because all 3 reference types can be used for filtering. And Insert/update will work as well, 'cause NHibernate exactly knows which value to use.

Related

nhibernate mapping, many to one

I have entity house which have besides other properties list of images.
Every image will be uploaded as a single action, one by one using js crop techniques.
Updated:
So one house can have many images.
public House
{
public Guid Id {get; set;}
....
List<Image> Images {get; set;}
}
public class Images
{
public House House {get; set;}
public string Path {get;set;}
public string Name {get;set;}
public string Description {get;set;}
}
My db tables are following:
House
Id
Name, ...
On this side I don't have relation to the Image table
Image table
Id
HouseId
Path
Name
Description
Is this approach ok?
How to map these objects using nhibernate orm?
Thanks
Declare your entities, and then in mapping files just associate Image HouseId with entity of House class. You should have foreign key in db. As xml answer already here, I'd add fluent nhibernate way.
public class House
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
....
}
public class Image
{
public virtual int Id { get; set; }
public virtual House HouseEntity { get; set; }
....
}
public class HouseMap : ClassMap<House>
{
public HouseMap()
{
Table("House");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name);
.....
}
}
public class ImageMap : ClassMap<Image>
{
public ImageMap()
{
Table("Image");
Id(x => x.Id).GeneratedBy.Identity();
References(x => x.House);
//References(x => x.House, "foreignKeyName"); There is also way to reference with specifying foreign key field
Map(x => x.Name);
....
}
}
House.hbm.xml:
<bag name="Images" cascade="all-delete-orphan" inverse="true">
<key column="HouseId" />
<one-to-many class="Image" />
</bag>
Image.hbm.xml:
<id type="int" column="Id">
<generator ... />
</id>
<many-to-one name="House" column="HouseId" cascade="none" />
Since you have a primary key in the images table and no Id property in your domain object, the id-mapping should be without the name-attribute.

Fluent-Nhibernate References and PropertyRef Doing a Select With Lazy Load

I am using PropertyRef for one of my References properties. With LazyLoad() it still does a Select and loads the User entity, even though I never "hit" the SalesPerson property.
Order Mapping
Id(x => x.Id).GeneratedBy.Native();
References(x => x.SalesPerson)
.LazyLoad()
.PropertyRef(x => x.Username)
.Column("rsm");
Map(x => x.Title);
Order Class
public class Order : BaseEntity
{
...
public virtual User SalesPerson { get; set; }
public virtual string Title { get; set; }
...
}
User Mapping
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Username).Column("login");
User Class
public class User : BaseEntity
{
public virtual string Username { get; set; }
...
}
Generated Order Mapping
<many-to-one class="Project.User" lazy="proxy" name="SalesPerson" property-ref="Username">
<column name="rsm" />
</many-to-one>
Executing Code
var order = session.Get<Order>(1);
Console.WriteLine(order.Title);
Is there anyway to prevent the Select to load the User entity when I'm not using the User entity?
Has to do with property-ref see
NHibernate creates proxy via session.Load(), but not via Linq or Criteria API
And not that you asked, but also consider that many-to-one with proxy does not allow you to do type-checking if you subclass User, see
http://ayende.com/Blog/archive/2010/01/28/nhibernate-new-feature-no-proxy-associations.aspx
I don't think this is a bug in NHibernate. It depends on your mapping.
First, remember that the reference map will join the key (primary key and foreign key) between 2 mapping tables. To prevent SELECT + 1, just ignore the key joint.
References(x => x.SalesPerson)
.LazyLoad()
.PropertyRef(x => x.Username)
WithForeignKeyName("none") //disable key join.

Nhibernat mapping aspnet_Users table

My User table I want to map to aspnet_Users:
<class name="User" table="`User`">
<id name="ID" column="ID" type="Int32" unsaved-value="0">
<generator class="native" />
</id>
<property name="UserId" column="UserId" type="Guid" not-null="true" />
<property name="FullName" column="FullName" type="String" not-null="true" />
<property name="PhoneNumber" column="PhoneNumber" type="String" not-null="false" />
</class>
My aspnet_Users table:
<class name="aspnet_Users" table="aspnet_Users">
<id name="ID" column="UserId" type="Guid" />
<property name="UserName" column="UserName" type="string" not-null="false" />
</class>
I tried adding one-to-one, one-to-many and many-to-one mappings. The closest I can get is with this error: Object of type 'System.Guid' cannot be converted to type 'System.Int32'.
How do I create a 1 way mapping from User to aspnet_User via the UserId column in User?
I am only wanting to create a reference so I can extract read-only information, affect sorts, etc. I still need to leave UserId column in User set up like it is now. Maybe a virtual reference keying off of UserId? Is this even possible with Nhibernate?
Unfortunately it acts like it only wants to use ID from User to map to aspnet_Users. Changing the table User to have it's primary key be UserId instead of ID is not an option at this point.
Any help would be greatly appreciated. Thanks in advance.
Aspnet_user table has a primary key of type Guid.
Your table User has a primary key of type Int32.
You can look at this as your user table has a reference (one to one) to aspnet_users table.
But if you look at the aspnet_users table as if it is a 'basket of values' to assign to a user (like if you'd have a type of user (admin, moderator, ...), but that would be many-to-one refrence (many users have one of the type) you can map it like that.
So:
class User
{
public virtual int ID { get; set; }
public virtual Guid UserId { get; set; }
public virtual string FullName { get; set; }
public virtual string PhoneNumber { get; set; }
}
class AspnetUser
{
public virtual Guid ID { get; set; }
public virtual string UserName { get; set; }
}
mapping would be (using fluent.NHibernate):
public class User_Map : ClassMap<User>
{
public User_Map()
{
Table("User");
ID(x => x.ID).GeneratedBy.Native();
References(x => x.UserId);
Map(x => x.FullName);
Map(x => x.PhoneNumber);
}
}
public class AspnetUser_Map : ClassMap<AspnetUser>
{
public User_Map()
{
Table("aspnet_Users");
ID(x => x.ID).GeneratedBy.Assigned();
Map(x => x.UserName);
}
}
To get some further guidance on this, have a look at the NHibernateProvider project. They have created a full membership provider implementation that uses NHibernate under the hood.

How can I use a composite-id with a class as the id field in fluent nhibernate?

I've got a mapping file like this:
<class name="Resource" table="resource" discriminator-value="null">
<composite-id name="Key" class="Models.Bases.ClientKey, Models">
<key-property name="Id" column="ID" type="int"/>
<key-property name="SiteId" column="clientID" type="int"/>
</composite-id>
<property name="Name" type="String">
<column name="`name`" length="500" sql-type="varchar" not-null="false"/>
</property>
</class>
which works just fine and here's the id class:
public class ClientKey
{
public int Id { get; set; }
public int ClientId { get; set; }
}
public class Resource
{
public virtual ClientKey Key { get; set; }
public virtual string Name { get; set; }
}
How can I remap this using FluentNhibernate? This code doesn't work:
WithTable("resource");
UseCompositeId()
.WithKeyProperty(x => x.Key.Id, "ID")
.WithKeyProperty(x => x.Key.ClientId, "clientID");
Map(x => x.Name);
It throws this error:
Could not find a getter for property 'Id' in class 'Models.Resource'
Thanks!!!
I'm afraid you can't fix it without modifying Resource class. I have checked with Fluent NHibernate's source - here's what code that outputs composite-id part looks like:
XmlElement element = classElement.AddElement("composite-id");
foreach( var keyProp in keyProperties )
{
keyProp.Write(element, visitor);
}
What is missing is "name" attribute, which should be set to "Key". Without this attibute, NHibernate fallbacks to default property name = "Id". As your class doesn't have Id property, NHibernate doesn't know what to do and throws an exception.
If you can't modify Resource class, you would have to use hbm mapping for this class or create a patch to fluent nhibernate (it is possible that this is known issue and someone's working on it - refer to fluent nhibernate's issue tracker).

Fluent NHibernate - Map 2 tables to one class

I have a table structure something like this
table Employees
EmployeeID
EmployeeLogin
EmployeeCustID
table Customers
CustomerID
CustomerName
What i would like is to map the structure above to one single class named:
Class Employee
EmployeeID
EmployeeLogin
EmployeeName
How do i do that with fluent nhibernate ?
I don't know if it is possible with fluent, but in xml you use the join element:
simplified:
<class name="Employee" table="Customers" >
<id name="CustomerID" .../>
<property name="CustomerName"/>
<join table="Employees">
<key column="EmployeeCustID" />
<property name="EmployeeLogin" />
</join>
</class>
See this post by Ayende
I agree with Frans above but if you're stuck with someone else's code and have to use the existing structure, you can can use WithTable.
public class EmployeesMap : ClassMap<Employees>
{
public EmployeesMap()
{
Id(x => x.EmployeeId);
Map(x => x.EmployeeLogin);
WithTable("Customers", join =>
{
join.Map(m => m.EmployeeName, "CustomerName");
join.WithKeyColumn("EmployeeCustID");
});
}
}
[DataContract(IsReference = true)]
public class Employees
{
[DataMember]
public virtual int EmployeeId { get; set; }
[DataMember]
public virtual string EmployeeLogin { get; set; }
[DataMember]
public virtual string EmployeeName { get; set; }
}
I have not tried this since Fluent NHibernate went to 1.0 so my syntax may be incorrect. I'm pretty sure this will only work if Customer.CustomerId is a foreign key to Employee.
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Id(x => x.EmployeeId);
Map(x => x.EmployeeLogin);
Table("Customer", m =>
{
m.Map(x => x.EmployeeName, "CustomerName");
});
}
}
Is EmployeeCustID unique? If not, this is never going to work, as you then try to cram two different entity types into 1. Also, with your structure, how do you want to save an instance? -> the CustomerID isn't known, so you can't save such an entity.
IMHO it's better to simply keep Customer as a related entity to Employee, as (I assume) the EmployeeCustID is used to link a Customer entity to an Employee entity if the employee is also a customer, which means 'customer' is just a role for employee and therefore optional and changeable and thus should be a separate entity.