Only pull Top X using Fluent NHibernate HasMany - nhibernate

I have the following mapping in Fluent-NHibernate Map
public class PostMap : ClassMap<Post>
{
public PostMap()
{
Id(i => i.Id).GeneratedBy.GuidComb();
Map(x => x.SiteId);
Map(x => x.Message);
Map(x => x.DateCreated);
Map(x => x.DateModified);
HasMany(x => x.Comments)
.OrderBy("DateCreated DESC")
.ReadOnly()
.Not.LazyLoad();
}
}
There are a lot of comments on each post, what I would like to do is only bring back the top 5 from the db rather than remove them in code.

You can do it manually. Ignore the property Comments from Post mapping, and load it manually with Take(X):
// query posts
foreach (var post in posts)
{
post.Comments = Session.QueryOver<Comments>()
.Where(x => x.PostId == post.Id)
.Take(X)
.Future();
}
With Future instead of List it will do one DB roundtrip for all comments.

Should be possible with filters. Read here: http://nhibernate.info/doc/nh/en/index.html#filters

Related

nHibernate One to One property null when loaded

I have a One to One relation between a TimeRecord and the Location.
This implementation is exactly the same es described in documentation:
https://github.com/jagregory/fluent-nhibernate/wiki/Fluent-mapping
public class TimeRecordMap : ClassMap<TimeRecord>
{
public TimeRecordMap()
{
Id(x => x.Id);
Map(x => x.Description);
Map(x => x.StartTime);
Map(x => x.EndTime);
HasOne(x => x.Location).Cascade.All();
}
}
public class LocationMap : ClassMap<Location>
{
public LocationMap()
{
Id(x => x.Id);
Map(x => x.Longitude);
Map(x => x.Latitude);
Map(x => x.Adress);
References(x => x.TimeRecord).Unique();
}
}
Now I query my TimeRecords with the following method:
public IList<TimeRecord> GetTimeRecords(string userid)
{
var query = Session.Query<TimeRecord>().Where(tr => tr.User.Id == userid);
return query.ToList();
}
Unfortunalelty my Location object is always null even if there is a coresponding entry in Location table but when I query for the coresponding Location with the desired TimeRecordId it is returned correctly.
See code here (code is inside a loop -> trCurrent is the current object in list received from "GetTimeRecords")
Location location = _locationRepo.getLocationByTimeRecordId(trCurrent.Id);
//trCurrent.Location = location; <- don't want to do it that way
if (trCurrent.Location != null)<- always null
{
... do stuff here
}
Implementation of my LocationRepository method:
public Location getLocationByTimeRecordId(int timeId)
{
var query = Session.Query<Location>()
.Where(tr => tr.TimeRecord.Id == timeId && tr.IsDeleted == false);
List<Location> lstReturn = query.ToList();
if (lstReturn.Count() == 0)
{
return null;
}
else
{
return lstReturn.First();
}
}
Can someone tell me why my Location is not resolved corretly?
Cheers,
Stefan
People claim that
HasOne / one-to-one is usually reserved for a special case. Generally, you'd use a References / many-to-one relationship in most situations (see: I think you mean a many-to-one). If you really do want a one-to-one, then you can use the HasOne method.
If you really do want a one-to-one and use it, you should remember that entities are joined by their ids by default.
If you check generated SQL you'll see something like JOIN Location ON Location.Id = TimeRecord.Id.
In order to get SQL like JOIN Location ON Location.TimeRecordId = TimeRecord.Id you should specify the foreign key via PropertyRef() method. So your mapping could be the folloving:
public class TimeRecordMap : ClassMap<TimeRecord>
{
public TimeRecordMap()
{
Id(x => x.Id);
Map(x => x.Description);
Map(x => x.StartTime);
Map(x => x.EndTime);
HasOne(x => x.Location).Cascade.All().PropertyRef(it => it.TimeRecord);
}
}
public class LocationMap : ClassMap<Location>
{
public LocationMap()
{
Id(x => x.Id);
Map(x => x.Longitude);
Map(x => x.Latitude);
Map(x => x.Adress);
References(x => x.TimeRecord/*, "TimeRecordId"*/).Unique().Not.Nullable();
}
}
In order to make sure that any location has TimeRecord you can add .Not.Nullable() into your LocationMap class.

Parent-Child mapping with Fluent NHibernate does not insert children

I'm trying to map a very basic parent-child relation with Fluent NHibernate.
However, when analyzing the SQL, only the parent-INSERT statement is created.
The situation is a simple class with a list of other classes. No relation back to the parent is needed. The children needs to be inserted/updated when the parent is inserted/updated.
var room = new Room();
room.Name = "Room1";
room.Courses.Add(new Course(){ Name = "Course1"});
room.Courses.Add(new Course(){ Name = "Course2"});
using (var session = sessionFactory.OpenStatelessSession())
{
using (var transaction = session.BeginTransaction())
{
session.Insert(room);
transaction.Commit();
}
}
The mapping looks like this.
public class RoomMapping : ClassMap<Room>
{
public RoomMapping()
{
Table("Rooms");
Id(x => x.Id)
.GeneratedBy.SeqHiLo("seq_rooms", "1000");
Map(x => x.Name);
HasMany(x => x.Courses)
.Cascade.All();
}
}
public class CourseMap : ClassMap<Course>
{
public CourseMap()
{
Table("Courses");
Id(x => x.Id)
.GeneratedBy.SeqHiLo("seq_courses", "1000");
Map(x => x.Name);
}
}
I already played with multiple options of the HasMany, however non with any success.
Sorry people. I just found it out.
I'm working in a Stateless session. So no relationships are managed ;)

Problems with updating records through Many-To-Many mappings

I have a problem where I have a many-to-many mappings in my table structure creating headaches when trying to edit a simple record.
Example layout of where I am having problems:
Facilities Many-to-One Locations
Facilities One-to-Many Users
Users Many-to-Many Locations
Users One-to-Many PreviousPasswords
If I make a change to a Facilities record (Change the name field) I get the following error upon save:
collection [Users.PreviousPasswords] was not processed by flush()
Mapping looks like:
public FacilitiesMap()
{
Table("Facilities");
Id(x => x.ID);
Map(x => x.Name);
HasMany(x => x.Users).KeyColumn("FacilitiesID").Cascade.AllDeleteOrphan().Inverse();
HasMany(x => x.Locations).KeyColumn("FacilitiesID").Cascade.AllDeleteOrphan().Inverse();
}
public UsersMap()
{
Table("Users");
Id(x => x.ID);
Map(x => x.FirstName);
Map(x => x.LastName);
References(x => x.Facilities, "FacilitiesID").ForeignKey("ID");
HasMany(x => x.PreviousPasswords).KeyColumn("UsersID").Cascade.AllDeleteOrphan().Inverse();
HasManyToMany<Locations>(x => x.Locations)
.Schema("Members")
.Table("UsersToLocations")
.ParentKeyColumn("UsersID")
.ChildKeyColumn("LocationsID")
.LazyLoad()
.Cascade.SaveUpdate().Inverse();
}
public LocationsMap()
{
Table("Locations");
Id(x => x.ID);
Map(x => x.Name);
References(x => x.Facilities, "FacilitiesID").ForeignKey("ID");
HasMany(x => x.Patients).KeyColumn("LocationsID").Cascade.AllDeleteOrphan().Inverse();
HasManyToMany<Users>(x => x.Users)
.Schema("Members")
.Table("UsersToLocations")
.ParentKeyColumn("LocationsID")
.ChildKeyColumn("UsersID")
.Cascade.SaveUpdate();
}
public PreviousPasswordsMap()
{
Table("PreviousPasswords");
Id(x => x.ID);
Map(x => x.Password);
Map(x => x.DateTime);
References(x => x.Users, "UsersID").ForeignKey("ID");
}
The only way I can do a successful update to the Facilities record is if I use the following function to get the record before changing and saving it:
public Facilities GetFacility(int id)
{
return FluentSessionManager.GetSession()
.CreateCriteria<Facilities>()
.Add(Expression.Eq("ID", id))
.SetFetchMode("Users", FetchMode.Eager)
.SetFetchMode("Locations", FetchMode.Eager)
.UniqueResult<Facilities>();
}
The problem with this method is that where there are 10,000 users it takes a long time to process this query. Or even worse, if we have 100 location as well, then it takes around 2 minutes to get the one Facilities record to edit.
I am sure there is some kind of issue in the Mapping. Not sure how to fix or even where to start. Any help with this would be greatly appreciated.
Thanks in advance.
Do you really need all the users for the facility? When you only add users you can use
HasMany(x => x.Users).ExtraLazyLoad();
and to improve the query when really all subcollections are needed
public Facilities GetFacility(int id)
{
var session = FluentSessionManager.GetSession();
// ignore the result, we only want to cache the results in the session
session.CreateCriteria<Facilities>()
.Add(Expression.Eq("ID", id))
.SetFetchMode("Users", FetchMode.Eager)
.Future<Facilities>();
return session.CreateCriteria<Facilities>()
.Add(Expression.Eq("ID", id))
.SetFetchMode("Locations", FetchMode.Eager)
.FutureValue<Facilities>().Value;
}

Fluent Mapping : using join-table and cascades

having a little trouble with a mapping for the following table setup currently:
Shop
[1] [1]
/ \
[n] [n]
Category-[m]---[n]-Article
The behaviour should be the following :
1 - when deleting a shop, all Articles and Categories Should be deleted
2 - when deleting a Category, related Articles should be unassigned but not deleted
3 - when deleting an Article, related Categories should be unassigned but not deleted
Here's the current mapping:
public class ShopMap: ClassMap<Shop>
{
public ShopMap()
{
this.Table("shop");
Id(x => x.Id).Column("id").GeneratedBy.Native();
Map(x => x.Name).Column("name");
HasMany(x => x.Categories).Cascade.AllDeleteOrphan;
HasMany(x => x.Articles).Cascade.AllDeleteOrphan;
}
}
public class CategoryMap: ClassMap<Category>
{
public CategoryMap()
{
this.Table("category");
Id(x => x.Id).Column("id").GeneratedBy.Native();
Map(x => x.Name).Column("name");
References(x => x.Shop);
HasManyToMany(x => x.Articles).Cascade.AllDeleteOrphan()
.Table("article_category")
.ChildKeyColumn("article_id")
.ParentKeyColumn("category_id")
.Inverse();
}
}
public class ArticleMap: ClassMap<Article>
{
public ArticleMap()
{
this.Table("article");
Id(x => x.Id).Column("id").GeneratedBy.Native();
Map(x => x.Name).Column("name");
References(x => x.Shop);
HasManyToMany(x => x.Categories).Cascade.All()
.Table("article_category")
.ParentKeyColumn("article_id")
.ChildKeyColumn("category_id");
}
}
When deleting a Category (Session.Delete()), NH tries to delete the related Articles as well. Changing the Cascade-Mode to SaveUpdate will fix this, but will leave the entries in the link table *article_category*. Summing up : Cascade.SaveUpdate is too lazy, Cascade.All is too eager.
I tried everything that came to my mind in the mappings, but couldn't find a correct way to map this (rather simple schema).
Any ideas on how to (fluently) map this are greatly appreciated!
Thanks in advance
Sebi
The entries are left in the link table because Category.Articles is defined as the inverse side of the relationship. You need to remove the Category from Article.Categories before deleting it in order for the link record to be removed.

Fluent nhibernate table-per-hierarchy mapping

I have a simple scenario where I have an entity Action (this is a workflow style application) that has a DueDate calculated property.
Now id like to introduce a SlidingAction, whose only difference (at this stage) is to override the DueDate calculation, as such has none of its own mapping.
Im having difficulty mapping this scenario since Fluent Nhibernate seems to be forcing me to map 'something' on the subclass.
Could someone shed some light?
Cheers,
Byron
public class ActionMap : ClassMap<Action>
{
public ActionMap()
{
WithTable("Actions");
Id(x => x.ID);
Map(x => x.Description);
Map(x => x.TimeLine);
Map(x => x.Template);
Map(x => x.StageOrder);
Map(x => x.CorrespondenceType).CustomTypeIs(typeof (ActionCorrespondenceTypeEnumType));
References(x => x.Matter).FetchType.Join();
HasMany(x => x.FileNotes).Cascade.SaveUpdate();
DiscriminateSubClassesOnColumn("Type")
.SubClass<SlidingAction>(/*its forcing me to map something here*/);
}
}
Just put an empty lambda in, c => {}.
.SubClass<SlidingAction>(c => {});