Not sure how to join tables using Fluent NHibernate - nhibernate

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"

Related

NHibernate join 2 tables and project to DTO with collection

I am trying to join 2 tables, and project directly to DTOs (NHibernate 5).
I have following entities:
public class Person {
public Guid Id {get;set;}
public string Name {get;set;}
}
public class Car {
public Guid Id {get;set;}
public string Brand {get;set;}
public Person Owner {get;set;}
}
as we see, there is just reference from Car to Person (car knows its owner), and this is ok in my whole project.
However, there is one place, where I need to query all Persons, and make each person with collection of owned cars.
I created such DTOs:
public class PersonDto {
public Guid Id {get;set;}
public string Name {get;set;}
public IList<CarDto> {get;set;}
}
public class CarDto {
public Guid Id {get;set;}
public string Brand {get;set;}
}
it is kind of present the data linked together upside-down.
This task seems trivial using SQL or LINQ (GroupJoin) however I found it extremly hard to do in NH, since GroupJoin is not implemented in NH.
Can you please help me how to solve above issue?
Just add a Car collection to the existing Person entity and mark the collection inverse. Collections in NHibernate are lazy by default, so when you query Person, it won't read the cars from the DB, until you start iterating them. In other words adding the Car collection won't affect the way your code works.
When you want to efficiently query persons together with cars, force NH to do a join
.QueryOver<Person>.Fetch(person => person.Cars).Eager
I'd like to answer my own question. Thanks Rafal Rutkowski for his input.
To get data that has association in only 1 direction, we need to introduce a new data model, and then, manually convert result to our entities. I hope following example is best possible answer:
1) create such data model to store NH response:
private class PersonCarModelDto
{
public Guid PersonId { get; set; }
public string PersonName { get; set; }
public Guid CarId { get; set; }
public string CarBrand { get; set; }
}
2) create such a model to store hierarchical data as output:
private class PersonModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public IList<CarModel> Cars { get; set; }
}
private class CarModel
{
public Guid Id { get; set; }
public string Brand { get; set; }
}
3) now the NH query:
Person personAlias = null;
Car carAlias = null;
PersonCarModelDto resultAlias = null;
var response = GetCurrentSession().QueryOver<Car>(() => carAlias) // notice we are going from from 'downside' entity to 'up'
.Right.JoinAlias(c => c.Owner, () => personAlias) // notice Right join here, to have also Persons without any car
.SelectList(list => list
.Select(() => personAlias.Id).WithAlias(() => resultAlias.PersonId)
.Select(() => personAlias.Name).WithAlias(() => resultAlias.PersonName)
.Select(() => carAlias.Id).WithAlias(() => resultAlias.CarId)
.Select(() => carAlias.Brand).WithAlias(() => resultAlias.CarBrand)
.TransformUsing(Transformers.AliasToBean<PersonCarModelDto>())
.List<PersonCarModelDto>()
;
4) now we have flat data as a list of PersonCarModelDto, but we want to make output model:
var modelResult = response.GroupBy(p => p.PersonId)
.Select(x => new PersonModel
{
Id = x.Key,
Name = x.Select(y => y.PersonName).First(), // First() because each PersonName in that group is the same
Cars = x.Select(y => new CarModel
{
Id = y.CarId,
Name = y.CarBrand
})
.ToList()
})
.ToList()
;
Conclusions:
the problem is much easier to solve having bidirectional associations
if for some reason, you don't want bidirectional associations, use this technique
this approach is also useful if your entities has a lot of other properties, but for some reason you need just a small part of them
(optimiziation of data returned for DB)

NHibernate Linq Startwith generates outer join

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.

NHibernate mapping returns null value

I have a problem with mapping in NHibernate.
The Order table has the Invoice_Id column which is the nullable FK to the Invoice table.
The problem is, when I load an Invoice which Id exists in the Order table, I see that ConnectedOrder property is null, why?
public class Invoice
{
public virtual Order ConnectedOrder { get; set; }
}
public class Order
{
public virtual Invoice ConnectedInvoice { get; set; }
}
public class InvoiceMap : ClassMap<Invoice>
{
public InvoiceMap()
{
this.References(x => x.ConnectedOrder).Nullable();
}
}
public class OrderMap : ClassMap<Order>
{
public OrderMap()
{
this.References(x => x.ConnectedInvoice).Nullable();
}
}
edit
I've changed my classes and mappings like Radim Köhler said, then I found that topic
Fluent NHibernate One-To-Many Mapping
and there was the need to also add:
this.HasMany(x => x.Orders)
.KeyColumn("Invoice_id")
.Inverse()
.Cascade
.AllDeleteOrphan();
and now it works
You may not like it, but the table structure described above, is not representing Entity relations you've created (so called one-to-one).
In case, that one table contains column referencing the another table (FK), we have scenario:
Each Order has exactly one (or null) Invoice. (many-to-one)
Invoice can be referenced by none or one or many Orders. (one-to-many)
That means, that we should express Entities like this:
public class Invoice
{ // many orders could reference us
public virtual IList<Order> Orders { get; set; }
...
public class Order
{ // unchanged
public virtual Invoice ConnectedInvoice { get; set; }
...
And the mapping should be:
public InvoiceMap()
{ // HasMany is one-to-many
this.HasMany(x => x.Orders)
...
}
public OrderMap()
{ // References is many-to-one
this.References(x => x.ConnectedInvoice).Nullable();
...

(Fluent) NHibernate: Map field from separate table into object

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);
}

Join between two non-key fields

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");