I'm trying to fetch a collection of read-only objects from NHibernate where all the properties come from a single table (Answers), with the exception of one property that comes from another table (Questions) in a many to one relationship. The fact it's two tables is an implementation detail that I want to hide, so I want the repository to return a sensible aggregate. The trouble is this requires I have two classes, one for each table, that NHibernate returns and then I have to select/map that into a third class that my repository returns. This feels a bit rubbish so instead I was hoping to have a mapping that joins the two tables for me but maps all the columns onto a single class. My mapping looks like this:
public QuestionAnswerMap()
{
ReadOnly();
Table("Question");
Id(x => x.Id).Column("questionId").GeneratedBy.Identity();
Map(x => x.AnswerShortCode).Column("AnswerShortCode");
Join("Answers", join =>
{
join.Fetch.Join();
join.KeyColumn("questionId").Inverse();
join.Map(x => x.QuestionId).Column("QuestionId");
join.Map(x => x.AnswerId).Column("AnswerId");
join.Map(x => x.MemberId).Column("MemberId");
});
}
The SQL this generates looks perfect and returns exactly what I want, but when there are multiple Answers that join to the same row in the Questions table, NHibernate seems to map them to objects wrongly - I get the right number of results, but all the Answers that have a common Question are hydrated with the first row in sql result for that Question.
Am I doing this the right way? NH is generating the right SQL, so why is it building my objects wrong?
because Join was meant to be this way. It assumes a one to one association between the two tables which are not the case.
Instead of a mapped Entity i would prefere a on the fly Dto for this:
var query = session.Query<Answer>()
.Where(answer => ...)
.Select(answer => new QuestionAnswer
{
QuestionId = answer.Question.Id,
AnswerShortCode = answer.Question.AnswerShortCode,
AnswerId = answer.Id,
MemberId = answer.MemberId,
});
return query.ToList();
Related
I have created a new extension with the extension builder which includes some tables and inline relations between some tables.
For reporting I need some queries where the selection condition is based on the related records.
Here I have difficulties to get the data (except doing raw queries, which seems wrong).
attempt:
do the query in the related records and extract the field which holds the uid of the parent records.
Problem: in the query result is no field with the uid of the parent record (although it is in the database).
This is understandable as there are no methods to get/set the relation. (But it does not change if I insert these methods.)
Also in the children record there is no TCA definition of the relation field except:
'parent' => [
'config' => [
'type' => 'passthrough',
],
],
which might be the reason the field can not be displayed in the record even if inserted in the showitem list.
attempt:
doing a join from the parent record.
Here I have not found any examples how to build a join.
There are other possibilities for a query, but the plainest seems this in the parent record repository:
public function getParentsByFilter($mainCondition,$subCondition) {
$parentQuery = $this->createQuery();
$parentQuery->matching(
$parentQuery->logicalAnd(
$parentQuery->equals('parent_field',$mainCondition)
// $childQuery->equals('children_field',$subCondition)
)
);
// $parentQuery->joinTable('tx_myext_domain_model_children', ...)
$parentQuery->setOrderings(['crdate'=> QueryInterface::ORDER_DESCENDING]);
$parentQuery->getQuerySettings()->setRespectStoragePage(false);
return $parentQuery ->execute();
}
Here I don't know how to join the children table.
I expect some methods like the commented lines, but have not found anything like it.
What's about the same configuration like in the table sys_category?
Ok, I am a little stumped on this NHibernate query. The confusion is around PasswordResetToken.
Firstly, here is the mapping:
public ContactMap()
{
Table("Contact");
Id(x => x.ContactId, "ContactId").Unique().GeneratedBy.Increment();
Map(x => x.EmailAddress);
...
Map(x => x.JobTitle);
References(x => x.PasswordResetToken, "EmailAddress")
.PropertyRef(x => x.EmailAddress)
.Cascade.None()
.Not.LazyLoad()
.Not.Update();
HasMany(x => x.Roles)
.Table("tblContactRole").KeyColumn("ContactId").Element("Role", part => part.Type<global::NHibernate.Type.EnumStringType<ContactRoles>>())
.AsSet()
.Not.LazyLoad();
}
Now here is the query:
public IList<Contact> GetContacts(int id)
{
var contacts = Session.CreateCriteria<Contact>()
.Add(Restrictions.Eq("Id", id))
.Add(Restrictions.Eq("IsActive", true))
.SetFetchMode("Roles", FetchMode.Eager)
.SetFetchMode("PasswordResetToken", FetchMode.Eager)
.SetResultTransformer(CriteriaSpecification.DistinctRootEntity)
.List<Contact>();
return contacts;
}
My understanding is that FetchMode.Eager means a JOIN is used instead of a SUBSELECT, so there isn't any reason there for extra calls to the db to appear.
A correct SQL query is run returning all the information required to hydrate a Contact as evidenced from the screenshot from NHProf (the highlighted query) (dont' worry about different table names etc - I have sanitized the code above):
What I don't understand is why on earth dozens of separate selects to the PasswordResetToken table are generated and run?? One of these queries is only generated for every contact that doesn't have a PasswordResetToken (ie. the first query returns nulls for those columns) - not sure what this has to do with it.
A contact might or might not have a few roles (superfluous to this issue) and similarly, may or may not have exactly one PasswordResetToken.
The DB is a little dodgy with few foreign keys. The link between Contact and PasswordResetToken in this case is a simple shared column "EmailAddress".
All these queries are generated on the running of that single line of code above (ie. that code is not in a loop).
Let me know if I am missing any info.
What should I be googling?
It's a bug. I would try to get it to work with just two queries, although from the bug report it sounds like that will be a challenge.
The attached test shows that a many-to-one association referencing an unique property (instead of the Id) results in a select n+1 problem. Although the first statement contains the correct join, all associated entities are fetched one by one after the join select. (Entities with the same value in the unique column are even fetched more than once.)
The interesting point is that this bug only occurs if the referenced
entities are already in the session cache. If they are not, no
additional select statements are created.
a Venue can have one or more Areas and, inversely, each Area has exactly one Venue. I'm using build 1.0.0.636 of Fluent NHibernate.
When I added Fetch.Join() to the VenueMap to eliminate lazy loading, the SQL generated includes a join back to the Area table, from itself: (simplified, obviously)
SELECT *
FROM Areas
LEFT OUTER JOIN Venues on Areas.VenueId = Venues.Id
LEFT OUTER JOIN Areas as Areas2 on Venues.Id = Areas2.VenueId
This causes it to return duplicate rows so each Venue has duplicate Areas in it's collection. I've tried using MoreLinq's DistinctBy after getting all Areas, but this still leaves each Venue having duplicate Areas - it just removes duplicate areas from the main collection. It also feels really filthy as I'm returning about three times as much data and then throwing it away.
As I alluded to above, my query is to get all Areas and have all Venues eager loaded.
Domain entities:
class Venue
{
int Id;
IList Areas;
}
class Area
{
int Id;
Venue Venue;
}
Mappings:
public class VenueMap : ClassMap<Venue>
{
public VenueMap()
{
Table("Venues");
HasMany(v => v.Areas)
.KeyColumn("VenueId")
.Inverse()
.Fetch.Join();
}
public class AreaMap : ClassMap<Area>
{
public AreaMap()
{
Table("Areas");
Id(a => a.Id);
References(a => a.Venue).Column("VenueId").Not.Nullable();
}
}
Any ideas how I can sort my mappings out and remove the duplicate Areas? I've done sooo much Googling already...
Thanks,
Monty
So does specifying .Not.LazyLoad() rather than Fetch.Join() in VenueMap on the Areas HasMany not work?
UPDATE
What might work better is to get rid of the Fetch.Join() in your mapping and to use a Criteria query like so.
session.CreateCriteria<Area>()
.CreateAlias("Venue", "venue", JoinType.LeftOuterJoin)
.List<Area>()
Before asking I have looked at all relevant posts on this topic
I have also read this blog post: http://ayende.com/Blog/archive/2007/12/23/NHiberante-Querying-Many-To-Many-associations-using-the-Criteria-API.aspx
I have Teams and I have Members, there is many-to-many relationship between them
Basically: Member -> MemberTeam <- Team
With my query I try to get all members that belong to same team as queried member (including queried member)
I have created my tables using Following FluentHibernate:
TeamMap code:
Id(x => x.ID).GeneratedBy.GuidComb().UnsavedValue("00000000-0000-0000-0000-000000000000");
HasManyToMany(x => x.Members)
.Table("MemberTeam")
.ChildKeyColumn("TeamID")
.ParentKeyColumn("MemberID");
MemberMap code:
Id(x => x.ID).GeneratedBy.GuidComb().UnsavedValue("00000000-0000-0000-0000-000000000000");
HasManyToMany(x => x.Teams)
.Table("MemberTeam")
.ChildKeyColumn("MemberID")
.ParentKeyColumn("TeamID");
The code I do my query with is:
DetachedCriteria dCriteria = DetachedCriteria.For(typeof(Team), "team")
.SetProjection(Projections.Id())
.Add(Property.ForName("team.ID").EqProperty("mt.ID"));
ICriteria criteria = Session.CreateCriteria(typeof (Member), "member")
.CreateAlias("Teams", "mt")
.Add(Subqueries.Exists(dCriteria))
.Add(Restrictions.Eq("mt.MemberID", new Guid(memberID)));
IList<Member> list = criteria.List<Member>();
I know that I'm doing something wrong, but I can not understand what it its
Any help would be appreciated
Thank You very much!
P.S. My map seams to be fine, I can save objects just fine!
By virtue of calling CreateAlias for the Teams path, NHibernate will join the appropriate tables for you. There's no need to do the subquery on member IDs:
var members = session
.CreateCriteria<Member>("member")
.CreateAlias("Teams", "mt")
.List<Member>();
However, you're not even using the alias, so you might just prefer to eagerly get the teams:
var members = session
.CreateCriteria<Member>("member")
.SetFetchMode("Teams", FetchMode.Eager)
.List<Member>();
Doing so will ensure you do not hit the database again when accessing each Member's Teams collection.
I have a simple model class (Part), which pulls from it's information from a single table (t_Part).
I would like a subclass of this model called (ProducedPart), that would still utilize NHibernate's caching mechanisms, but would only be instances of (Part) that have a foreign key relationship in a table called "t_PartProduction". I do not need to have a model for this second table.
I only need a read-only version of ProducedPart
I could always implement a Facade/Repository over this, but I was hoping to setup a mapping that would pull "t_Part" joined with "PartProduction" when I asked for "ProducedPart" in NH.
Is this the wrong way to use NH?
Edit
So, the SQL would look something like
SELECT p.*
FROM t_Part p
INNER JOIN t_PartProduction pp ON pp.PartID = p.PartID
WHERE pp.ProductionYear = '2009'
I believe what you are looking for is a joined subclass. In FNH, it will look something like:
public class PartMap : ClassMap<Part>
{
public PartMap()
{
Id(x => x.Id)
JoinedSubClass<ProducedPart>("PartID", sub => {
sub.Map(x => x.Name);
sub.Map(x => x.ProductionYear);
});
}
}
In order have NHibernate cache the results, you will need to have the subclass mapped (and if you didn't map it, you wouldn't be able to get NH to load it in the first place).
Bringing in some context from the FNH groups thread, it will not explicitly be read-only though. In my opinion, making things read-only is not an appropriate thing for NHibernate to manage. This is better controlled by the database and connections (i.e. creating a connection to the database that only has SELECT permissions on the tables/views being accessed). See my answer to a previous SO question about readonly sessions in NHibernate for more of my thoughts on the matter.
The key here is using both the where and mutable elements of the class definition for NHibernate Mappings.
Using Fluent NHibernate, this looks like:
public Part()
{
WithTable("t_Part");
Id(i => i.Id).ColumnName("PartID");
Map(m => m.Name).ColumnName("Part");
SetAttribute("where", "PartID IN ( SELECT pp.PartID FROM t_PartProduction pp WHERE pp.ProductionYear = '2009' ) ");
ReadOnly();
}
No, this is perfectly possible. Look in the NHibernate documentation for the "table per subclass" model of inheritance. It will actually implement this as a LEFT JOIN, so that when you load a Part, it creates an instance of either your Part or your ProducedPart class depending on whether the other row is present. You'll find documentation on nhibernate.info.
I'm not sure you could make ProducedPart read-only doing this though.
I'm assuming from this:
WHERE pp.ProductionYear = '2009'
that you want the subclass only where the production year is 2009, i.e. if when there is a record in t_PartProduction for a different year, you want this Part treated as a plain Part object, not a ProducedPart, then you could consider creating a view definition within your database that is a filtered version of t_PartProduction, then making your subclass join to this view rather than the base table.