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);
}
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 intend to read the referencing field (ParentMenu) value for my self join mapping. The class and mappings are as follows;
public class MenuSetup
{
public virtual int MenuId { get; set; }
public virtual string DisplayText { get; set; }
public virtual int MenuOrder { get; set; }
public virtual bool MenuStatus { get; set; }
public virtual bool HasKids { get; set; }
public virtual MenuSetup Parent { get; set; }
public virtual ICollection<MenuSetup> SubMenu { get; set; }
}
Mappings below;
public MenuSetupMap()
{
Id(x => x.MenuId).GeneratedBy.Identity();
Map(x => x.DisplayText);
Map(x => x.MenuStatus);
Map(x => x.MenuOrder);
Map(x => x.HasKids);
HasMany(x => x.SubMenu).KeyColumn("ParentMenu");
References(x => x.Parent).Column("ParentMenu");
.Cascade.AllDeleteOrphan()
.Fetch.Join().Inverse().KeyColumn("MenuId");
}
There is ParentMenu field that i want to read like this into my view model HomeMenuViewModel.
var session = MvcApplication.SessionFactory.GetCurrentSession();
string qry = #"select p.MenuId,p.DisplayText,p.MenuStatus,p.MenuOrder,
m from MenuSetup as p left join p.Parent m";
var vm=session.CreateQuery(qry).List<object[]>()
.Select(x=>new HomeMenuViewModel()
{
MenuId=(int)x[0],
DisplayText=(string)x[1],
MenuStatus=(Boolean)x[2],
MenuOrder=(int)x[3],
ParentMenu = x[10] == null ? 0 : (int)x[10]
}).ToList();
throws an error "System.IndexOutOfRangeException was unhandled by user code". I am stuck really need help.
The query generated from NHProf is shown below;
select menusetup0_.MenuId as col_0_0_,
menusetup0_.DisplayText as col_1_0_,
menusetup0_.MenuStatus as col_2_0_,
menusetup0_.MenuOrder as col_3_0_,
menusetup1_.MenuId as col_4_0_,
menusetup1_.MenuId as MenuId10_,
menusetup1_.DisplayText as DisplayT2_10_,
menusetup1_.MenuStatus as MenuStatus10_,
menusetup1_.MenuOrder as MenuOrder10_,
menusetup1_.HasKids as HasKids10_,
menusetup1_.ParentMenu as ParentMenu10_
from [MenuSetup] menusetup0_
left outer join [MenuSetup] menusetup1_
on menusetup0_.ParentMenu = menusetup1_.MenuId
Waiting for help in earnest.
Thank you
UPDATE SOLUION
I found the solution after much tinkering
I needed to declare the field in the MenuSetup class as below
public class MenuSetup
{
.
.
public virtual int ParentMenu { get; set; }
.
}
In the Mapping class, i declared the column as well.
My retrieval code changed to
var session = MvcApplication.SessionFactory.GetCurrentSession();
string qry = #"select p.MenuId,p.DisplayText,p.MenuStatus,p.MenuOrder,
p.ParentMenu from MenuSetup as p";
var vm=session.CreateQuery(qry).List<object[]>()
.Select(x=>new HomeMenuViewModel()
{
MenuId=(int)x[0],
DisplayText=(string)x[1],
MenuStatus=(Boolean)x[2],
MenuOrder=(int)x[3],
ParentMenu = x[4] == null ? 0 : (int)x[4]
}).ToList();
The amount of object[] items is related to the HQL query, not to the generated SQL query. I.e. the above query will return object[] having 5 elements:
// the HQL Snippet
select
p.MenuId, // object[0]
p.DisplayText, // object[1]
p.MenuStatus, // object[2]
p.MenuOrder, // object[3]
m // object[4]
from MenuSetup ....
So, there is no object[10] ... and that's why we do have System.IndexOutOfRangeException
The 5th element is complete parent. The solution should be like this:
ParentMenu = x[4] == null ? null : (MenuSetup)x[4]
NOTE: it could be better if we would use only one property from parent, e.g. change HQL to ...m.DisplayText...
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"
I have a simple Fluent NHibernate model with two related classes:
public class Applicant
{
public Applicant()
{
Tags = new List<Tag>();
}
public virtual int Id { get; set; }
//other fields removed for sake of example
public virtual IList<Tag> Tags { get; protected set; }
public virtual void AddTag(Tag tag)
{
tag.Applicant = this;
Tags.Add(tag);
}
}
public class Tag
{
public virtual int Id { get; protected set; }
public virtual string TagName { get; set; }
public virtual Applicant Applicant { get; set; }
}
My fluent mapping is the following:
public class ApplicantMap : ClassMap<Applicant>
{
public ApplicantMap()
{
Id(x => x.Id);
HasMany(x => x.Tags).Cascade.All();
}
}
public class TagMap : ClassMap<Tag>
{
public TagMap()
{
Id(x => x.Id);
Map(x => x.TagName);
References(x => x.Applicant).Not.Nullable();
}
}
Whenever I try to update an applicant (inserting a new one works fine), it fails and I see the following SQL exception in the logs:
11:50:52.695 [6] DEBUG NHibernate.SQL - UPDATE [Tag] SET Applicant_id = null WHERE Applicant_id = #p0;#p0 = 37 [Type: Int32 (0)]
11:50:52.699 [6] ERROR NHibernate.AdoNet.AbstractBatcher - Could not execute command: UPDATE [Tag] SET Applicant_id = null WHERE Applicant_id = #p0 System.Data.SqlClient.SqlException (0x80131904): Cannot insert the value NULL into column 'Applicant_id', table 'RecruitmentApp.dbo.Tag'; column does not allow nulls. UPDATE fails.
Why is NHibernate trying to update the tag table and set Applicant_id to null? I'm at a loss on this one.
Set Applicant.Tags to Inverse will instruct NHibernate to save Tags after the Applicant.
public class ApplicantMap : ClassMap<Applicant>
{
public ApplicantMap()
{
Id(x => x.Id);
HasMany(x => x.Tags).Cascade.All().Inverse();
}
}
More detail:
Inverse (as opposed to .Not.Inverse()) means the other side of the relationship (in this case, each Tag) is responsible for maintaining the relationship. Therefore, NHibernate knows that the Applicant must be saved first so that Tag has a valid foreign key for its Applicant.
Rule of thumb: The entity containing the foreign key is usually the owner, so the other table should have Inverse
This is based on a legacy system.
I have the following tables:
CREATE TABLE a
id int
CREATE TABLE b
a_id int,
c_id int
relationshipid int -- must be IN (1, 2, 3)
CREATE TABLE c
id int
I want the following domain models
public class A
{
public int Id { get; set; }
public C entityc { get ; set; }
}
public class C
{
public int Id { get; set; }
}
Table b is set up so that for a particular defined relationshipid there is (well, should only be) one pair of ids. For other relationships, that one to one mapping through B doesn't hold true. Relationshipid can be one of a small number of values.
How do I get entity C into class A from the relationship where the relationshipid is 1 using fluent NHIbernate?
As a side question, is there a name for what I am trying to do here? The original approach was trying use a HasOne with a Join table and Filter the results, but obviously that failed miserably.
EDIT: Clarified RelationshipID and purpose.
I think the easiest way to map this would be to make your table b an entity and have references to both A and C within that entity and RelationshipId as the id. So your mappings would look something like this:
public class A
{
public int Id { get; set; }
public IList<B> bEntities { get; set; }
}
public class ClassAMap : ClassMap<A>
{
public AMap()
{
Table("A");
Id(x => x.Id);
HasMany(x => x.bEntities)
.KeyColumns.Add("a_id");
}
}
public class B
{
public virtual int RelationshipId { get; set; }
public virtual A InstanceA { get; set; }
public virtual C InstanceC { get; set; }
}
public class ClassBMap : ClassMap<B>
{
public BMap()
{
Table("B");
Id(x => x.RelationshipId , "relationshipid");
References(x => x.InstanceA);
References(x => x.InstanceC);
}
}
Edit:
If your wanting to filter these results for the collection of B entities in your A entity to only ones matching RelationshipId = 1 then you should take a look at this post:
Fluent NHibernate and filtering one-to-many relationship on query requiring multiple joins?
You could also do something like this in your class A:
public class A
{
public int Id { get; set; }
public IList<B> bEntities { get; set; }
public C InstanceC
{
get { return bEntities.First<B>(x => x.RelationshipId == 1).InstanceC; }
}
}