I'm working on putting together a simple POC app using Fluent NHibernate to attempt to show that it can do everything our current hand-rolled data access tool and so much more. One of the fringe cases that my boss is worried about is the ability to access multiple schemas within the same database in one query. So far I have been able to pull data from tables in both schemas so long as the query only touches one schema at a time. If I try to execute a command that will join tables from both schemas, it blows up.
Based on the error messages that I'm seeing, I don't believe that the problem is with joining across schemas, but rather with the fact that the two fields I need to join the tables on are both non-key fields. The structure of the two table is something like this:
Customer (in schema 1)
--------
int CustomerId (Primary Key)
string Name
...other fields
Order (in schema 2)
--------
int OrderId (primary key)
string CustomerName
...other fields
Using sql directly I can join on the Name/CustomerName fields and get the data from both tables. However, using NHibernate I keep getting an "System.FormatException : Input string was not in a correct format" when trying to pull data from the Order table and include data from the Customer table. This leads me to believe that NHibernate is trying to join on the CustomerName field and CustomerId field.
I know how to tell it to use the CustomerName field in my Order mapping, but I can't figure out a way to telling to join on the Name field of the Customer table.
My Mappings look something like this:
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id)
.Column("CustomerId");
Map(x => x.Name);
}
}
public class OrderMap : ClassMap<Order>
{
public OrderMap()
{
Schema("schema2");
Id(x => x.Id)
.Column("OrderID");
Map(x => x.CustomerName)
.Column("CustomerName");
References<Customer>(x => x.Customer, "CustomerName");
}
}
The SQL I'd write to get the results I want would be something like:
select o.OrderId, o.CustomerName, c.CustomerId
from order o
inner join customer c on c.Name = o.CustomerName
Is this even possible?
Is there a different/better way to go about this?
I haven't working with multiple schemas, but the approach I've found for mapping non-key fields is as follows:
In OrderMap...
References(order => order.Customer).Column("CustomerName").PropertyRef("Name");
Where PropertyRef("Name") is actually referring to the Name property on your Customer class (which you would define in CustomerMap).
I'm just getting started with FNH, so you may find a better solution, but I hope this helps.
I am giving example how can you Map NON key fields in Hibernate using annotation.
Please convert it to corresponding nHibernate.
CREATE TABLE `Customer` (
`CUSTOMER_ID` bigint(20) NOT NULL AUTO_INCREMENT,
`NAME` varchar(100) NOT NULL,
PRIMARY KEY (`CUSTOMER_ID`)
)
CREATE TABLE `Order` (
`ORDER_ID` bigint(20) NOT NULL AUTO_INCREMENT,
`CUSTOMER_NAME` varchar(100) NOT NULL,
PRIMARY KEY (`ORDER_ID`)
)
Customer Entity
#Entity
#Table(name = "CUSTOMER")
public class Customer{
private long customerId;
private String name;
private Order order;
public Customer() {
}
public Customer(String name) {
this.name= name;
}
#Id
#GeneratedValue
#Column(name = "CUSTOMER_ID")
public long getCustomerId() {
return this.customerId;
}
public void setCustomerId(long customerId) {
this.customerId= customerId;
}
#Column(name = "NAME", nullable = false, length = 100, insertable=false, updatable=false)
public String getName() {
return this.name;
}
public String setName(String name) {
return this.name;
}
#ManyToOne
#JoinColumn(name = "NAME", referencedColumnName = "CUSTOMER_NAME")
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order= order;
}
}
Order Entity
#Entity
#Table(name = "ORDER")
public class Order implements Serializable {
private long orderId;
private String customerName;
public Ortder() {
}
public Order(String customerName) {
this.customerName= customerName;
}
#Id
#GeneratedValue
#Column(name = "ORDER_ID")
public long getOrderId() {
return this.orderId;
}
public void setOrderId(long orderId) {
this.orderId= orderId;
}
#Column(name = "CUSTOMER_NAME", nullable = false, length=250)
public String getCustomerName() {
return this.customerName;
}
public void setCustomerName(String customerName) {
this.customerName= customerName;
}
}
Customer customer = new Customer("C1");
session.load(customer , 5L);
System.out.println(customer.getName());
Order order = customer.getOrder();
System.out.println(order.getCustomerName());
SQL will be generated like ( I have removed the alias generated by Hibernate)
select customer.CUSTOMER_ID, customer.NAME, order.ORDER_ID,
order.CUSTOMER_NAME
from CUSTOMER
left outer join ORDER **on NAME=CUSTOMER_NAME** where CUSTOMER_ID=?
Joining across schemas is no problem, you just need to specify the schema in your mapping:
public sealed class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Table("Customer");
Schema("dbo");
// etc.
}
}
Your order table should have CustomerId as a foreign key, not CustomerName. That's the standard way to implement a one-to-many relationship and is not particular to NHibernate. If you have that, the mapping in OrderMap is:
References(x => x.Customer, "CustomerId");
Related
I have two entities that are connected through one to many relationship.
like this example:
public class Category
{
public int Id {get; set; }
public string Name {get; set;}
}
public class Product
{
public int Id {get; set;}
public string Name {get; set;}
public Category ProductCategory {get; set;}
}
and the mapping using fluent nhibernate
public class CategoryMap : ClassMap<Category>
{
....
}
public Class ProductMap: ClassMap<Product>
{
...
References(x => x.ProductCategory).Column("CategoryId");
}
When I do search using linq to NHibernate with where clause and equal, it generates inner join between product and category
so this
var query = session.Query<Product>()
.Where (x => x.ProductCategory.Name == "abc");
this will generate inner join
but when I do search with startwith like this
var query = session.Query<Product>()
.Where (x => x.ProductCategory.Name.StartWith("abc"));
this will generate outer join between the two.
Why, and how can I force to generate inner join?
You can do it using QueryOver, which is another method to create query in NHibernate. In that case, you specify the join type as you want.
Category category = null;
var result = session.QueryOver<Product>()
.JoinAlias(x => x.ProductCategory, () => category, JoinType.InnerJoin)
.Where(Restrictions.Like(Projections.Property(() => category.Name), "abc%", MatchMode.Start))
.List();
On the other hand, query over is more verbose code, you have to specify a lot of things you avoid using linq.
I'm using NHibernate on legacy tables and after looking through our codebase, I seem to be the only person with this need. I need to join two tables so I can run a query, but I haven't made any progress today. I'll try to abbreviate where it makes sense in my code snippets. Care to help?
Tables--
Order
OrderID (primary key)
OrderName
OrderType
OrderLocation
OrderAppendix
ID (composite key)
Key (composite key)
Value (composite key)
The ID portion of the OrderAppendix composite key is associated with OrderID in the Order table; therefore, and Order can have several entries in the OrderAppendix table.
Domain Objects--
[Serializable]
public class Order
{
public virtual string OrderID { get; set; }
...
public virtual string OrderLocation { get; set; }
}
[Serializable]
public class OrderAppendix
{
public virtual string ID { get; set; }
public virtual string Key { get; set; }
public virtual string Value { get; set; }
public override bool Equals(object obj)
{
...
}
public override int GetHashCode()
{
...
}
}
Mapping
internal sealed class OrderMap : ClassMap<Order>
{
Table("Order");
Id(x => x.OrderID).Column("OrderID").Length(20).GeneratedBy.Assigned();
Map( x => x.OrderName).Column("OrderName")
....
}
internal sealed class OrderAppendixMap : ClassMap<OrderAppendix>
{
Table("OrderAppendix");
CompositeId()
.KeyProperty(x => x.ID, "ID")
....
Map( x => x.ID).Column("ID);
...
}
I won't muddy this up with my futile attempts at joining these tables, but what I would like to do is query by things like OrderType or OrderLocation given that the results all have the same Value from the OrderAppendix table.
Example desired SQL
SELECT * FROM ORDER
INNER JOIN
ON Order.OrderID = OrderAppendix.ID
WHERE OrderAppendix.Key = "Stuff"
Edit
Here's where I've gotten by reading the documentation on "QueryOver"
http://nhibernate.info/doc/nh/en/index.html#queryqueryover
Order Order = null;
OrderAppendix OrderAppendix = null;
resultList = session.QueryOver<Order>(() => Order)
.JoinAlias(() => Order.OrderAppendix, () => OrderAppendix)
.Where(() => OrderAppendix.Key == "MatchThis")
.List();
I think I'm on the right track using aliases to join the table, but obviously I haven't found a way to inform NHibernate of the many-to-one mapping that's needed. Also, you can see that I've added a property of type OrderAppendix to Order in order to the use the alias functionality.
Well, I didn't get why you did not used standard parent-child approach using HasMany on Order side and Reference of Appendix. This is because of composite key on appendix side?
If you would do that, you will be able to provide following HQL:
SELECT o FROM Order o
LEFT JOIN FETCH o.apxs a
WHERE a.Key="Stuff"
We have the following database structure:
UserTeam table
Id (PK, int not null)
UserId (FK, int, not null)
TeamId (FK, int, not null)
RoleId (FK, int, not null)
libRole table
Id (PK, int not null)
Description (varchar(255), not null)
And we have an entity as follows:
public class UserTeam
{
public int Id { get; set; }
public Team Team { get; set; }
public User User { get; set; }
public int RoleId { get; set; }
public string Role { get; set; }
}
We are using Fluent NHibernate and configuring NHibernate automatically (ie, using Automapping classes with overrides).
We are trying to get JUST the description column from the libRole table into the "Role" property on the UserTeam table, but really struggling. The following is the closest we have got:
public class UserTeamMap : IAutoMappingOverride<UserTeam>
{
public void Override( FluentNHibernate.Automapping.AutoMapping<UserTeam> mapping )
{
mapping.References( m => m.User ).Column( "UserId" );
mapping.References( m => m.Team ).Column( "TeamId" );
mapping.Join("Role", join =>
{
join.Fetch.Join();
join.KeyColumn( "Id" );
join.Map( x => x.Role, "Description" );
} );
}
}
Which generates the following SQL:
SELECT
TOP (#p0) this_.Id as Id70_0_,
this_.RoleId as RoleId70_0_,
this_.TeamId as TeamId70_0_,
this_.UserId as UserId70_0_,
this_1_.Description as Descript2_71_0_
FROM
[UserTeam] this_
inner join
libRole this_1_
on this_.Id=this_1_.Id;
Close, but NHibernate is using the Id column on both the UserTeam table and the libRole table in the join, when it should be doing on this_.RoleId=this_1_.Id
What are we missing? We don't really want to create a "libRole" entity within the application, as all we really care about is the description values - which are user configurable, so we can't just use an enum either. Can anyone help?
Join uses the primary key of the parent table. Its not possible to change this to a foreign key. See the docs for further details on what is possible with Join.
In this situation I would recommend creating an entity for the lookup. But if you really want to take this approach you could map the property with a formula, i.e.
Map(x => x.Role).Formula("(select description from libRole where Id = RoleId)");
Note this isn't perfect because it uses RoleId so if the query has another table with a column named RoleId then the DBMS will complain when trying to execute the SQL.
I'm currently trying to get (Fluent)NHibernate to map an object to our legacy database schema. There are three tables involved.
Table a contains most information of the actual object I need to retrieve
Table b is a table which connects table a with table c
Table c has one additional field I need for the object
An SQL query to retrieve the information looks like this:
SELECT z.ID, z.ZANR, e.TDTEXT
FROM PUB.table_a z
JOIN PUB.table_b t ON (t.TDKEY = 602)
JOIN PUB.table_c e ON (e.ID = t.ID AND e.TDNR = z.ZANR)
WHERE z.ZANR = 1;
The main problem is how to specify these two join conditions in the mapping.
Entity for table a looks like this:
public class EntityA
{
public virtual long Id { get; set; }
public virtual int Number { get; set; }
public virtual string Name { get; set; }
}
Name should map to the column table_c.TDTEXT.
The mapping I have so far is this:
public class EntityAMap : ClassMap<EntityA>
{
public EntityAMap()
{
Table("PUB.table_a");
Id(x => x.Id).Column("ID");
Map(x => x.Number).Column("ZANR");
}
}
I tried to map the first join with the same strategy as in How to join table in fluent nhibernate, however this will not work, because I do not have a direct reference from table_a to table_b, the only thing connecting them is the constant number 602 (see SQL-query above).
I didn't find a way to specify that constant in the mapping somehow.
you could map the name as readonly property easily
public EntityAMap()
{
Table("PUB.table_a");
Id(x => x.Id).Column("ID");
// if ZANR always has to be 1
Where("ZANR = 1");
Map(x => x.Number).Column("ZANR");
Map(x => x.Name).Formula("(SELECT c.TDTEXT FROM PUB.table_b b JOIN PUB.table_c c ON (c.ID = b.ID AND b.TDKEY = 602 AND c.TDNR = ZANR))");
}
Update: i could only imagine a hidden reference and the name property delegating to there
public EntityAMap()
{
HasOne(Reveal.Member<EntityA>("hiddenEntityC"));
}
public class EntityB
{
public virtual int Id { get; set; }
public virtual EntityA EntityA { get; set; }
}
public EntityCMap()
{
Where("Id = (SELECT b.Id FROM PUB.table_b b WHERE b.TDKEY = 602)");
References(x => x.EntityA).PropertyRef(x => x.Number);
}
This question is really to establish if what im trying to achieve is possible.
I have three tables i'll omit the actual names to keep it simple and less confusing
so we have
Person [
PID - uniqueidentifier PK
]
Applicant
[
PID - uniqueidentifier PK,
AID - varchar(20)]
Student
[
PID - uniqueidentifier PK,
SID - int]
both the student and applicant tables are related to the person table with a one-to-one relationship (where the primary key is held in the person table), but are not related to each other.
I have the following domain objects
public class Person
{
public virtual Guid PID{get;set;}
}
public class Applicant: Person
{
public virtual string AID {get;set;}
}
public class Student:Person
{
public virtual int SID {get;set;}
}
These each have a map class derived from either ClassMap or SubClassMap
public class PersonMap: ClassMap<Person>
{
public PersonMap()
{
Id(x => x.PID).GeneratedBy.Assigned();
Table("Person");
}
}
public class ApplicantMap: SubclassMap<Applicant>
{
public ApplicantMap()
{
Table("Applicant");
KeyColumn("PID");
Map(x => x.AID);
}
}
public class StudentMap:SubclassMap<Student>
{
public StudentMap()
{
Table("Student");
KeyColumn("PID");
Map(x => x.SID);
}
}
My question is, is it possible to have a person who is an applicant and a student at the same time.In database terms I can have a row in each table that holds the same PID, will nhibernate allow me to save and retrieve all three objects?
one of the problems I have is trying to insert into student when the id already exists for an applicant and of course person..
public void MakePersonAStudent(Person p)
{
Student newStudent = new Student();
newStudent.PID = p.PID;
newStudent.SID = getNewStudentID();
Session.Save(newPerson);
}
The exception generated is:
a different object with the same identifier value was already associated with the session .... 73e5fd90-c27a-49d8-87dc-cd6413c120a2, of entity: Student
use single_table inheritance. doc for hibernate: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/inheritance.html