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.
Related
I am trying to get a QueryOver working using a Projection on a many-to-one.
The class "Post" has a property many-to-one "Creator".
Using
session.QueryOver(Of Post).
Select(Projections.
Property(of Post)(Function(x) x.Creator).
WithAlias(Function() postAlias.Creator)).
TransformUsing(Transformers.AliasToBean(Of Post)()).
List()
works BUT each creator is retrieved by a single query rather than using a join like it is done when not using a select/projection. So if there are 5 posts with 5 different creators, 6 queries will be run 1 for the list of posts and 5 for the creators.
I tried to get it working using a JoinAlias but nothing really did the job.
I already searched for a solution, but all solutions I found did use the Linq-Provider which does not really fit since the actual "field list" is passed via a parameter.
Does anyone know if there is a solution to this other than the linq provider?
There is a solution, we can use projections for many-to-one and then custom result transformer.
DISCLAIMER: I can read VB syntax but do not have enough courage to write... I expect that you can read C# and convert it into VB....
So we can have projection like this:
// aliases
Post root = null;
Creator creator = null;
// projection list
var columns = Projections.ProjectionList();
// root properties
columns.Add(Projections.Property(() => root.ID).As("ID"));
columns.Add(Projections.Property(() => root.Text).As("Text"));
// reference properties
columns.Add(Projections.Property(() => creator.ID).As("Creator.ID"));
columns.Add(Projections.Property(() => creator.FirstName).As("Creator.FirstName"));
// so our projections now do have proper ALIAS
// alias which is related to domain model
// (because "Creator.FirstName" will be use in reflection)
var query = session.QueryOver<Post>(() => root)
.JoinAlias(() => root.Creator, () => creator)
.Select(columns)
Now we would need smart Transformer, our own custome one (plugability is power of NHibernate). Here you can find one:
public class DeepTransformer
And we can continue like this
var list = query
.TransformUsing(new DeepTransformer<Post>())
.List<Post>()
Check also this:
Fluent NHibernate - ProjectionList - ICriteria is returning null values
NHibernate AliasToBean transformer associations
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();
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.
I've got a table called AdministratorPrivilages that has the following fields:
ID
List item
MemberId
Value
MemberType
Now, the members can be of two types (Enterprise and Express). Enterprise members live in the enterprise table. Express members live in the expressmember table. I've tried to do my fluent mapping like so.
public class AdministratorPrivilegesMapping : ClassMap<AdministratorPrivileges>
{
public AdministratorPrivilegesMapping()
{
Id(x=>x.Id);
Map(x => x.Value).Column("Value");
ReferencesAny(x => x.Member)
.EntityTypeColumn("MemberType")
.EntityIdentifierColumn("MemberId")
.IdentityType<Int32>()
.AddMetaValue<ExpressMember>("Express")
.AddMetaValue<Member>("Enterprise");
}
}
Both member tables have integer ids with ascending values. When I try to pull back the privilages associated with enterprise member 10, I'm getting the permission set associated with Express Member 10. Both other tables are mapped with the old school hbm mapping files.
Am I missing something obvious? I'm using NHibernate 2.1 and FluentNhibernate 1.1
I actually found the solution using .Where().
My situation is a little different, I have objectA and objectB.
objectA contains objectB, but objectB also contains a collection of objectBs.
"objectA":{
"someProp" : "(string)",
"objectB":{
"someProp" : "(string)",
"comeCol" : [
"(objectB)"
]
}
}
So the "Parent" property of objectB can either be of type objectB or objectA, which is why I needed to use ReferencesAny in the mapping of objectB.
Mapping looks like
ReferencesAny( x => x.Parent )
.IdentityType< int >()
.MetaType< string >()
.EntityTypeColumn( "ParentType" )
.EntityIdentifierColumn( "ParentId" )
.AddMetaValue< objectA >( "E" )
.AddMetaValue< objectB >( "S" )
.Access.Property()
.LazyLoad()
.Cascade.All();
All this works well when saving, however, my problem occured when retrieving, because the framework wasn't told what to retrieve and simply retrieved everything.
So now here is the mapping of the collection that fixed the problem:
HasMany( x => x.objectBs )
.KeyColumn( "ParentId" )
.Where( "ParentType = 'S'" )
.Cascade.All()
.LazyLoad();
Hope it will help anyone in the same situation :)
As seems to always be the case when I post on Stackoverflow, I'm being a complete goober and missing something glaringly obvious that cleared itself up with a good night's rest and some caffeine. I needed to look into the mappings for the ExpressMember and Member classes. Turns out the bag declarations there didn't filter by the type of object appropriately. Initially, I thought I had made the breaking change, when in fact it was actually a very old issue. The collision in id numbers between the two different types of members was not an issue for a very long time, as express members typically have either all the permissions or none, as do most of the older members (which were created first under an admin/plebe scheme with privileges being broken out later).
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>()