Ensure a one to one (distinct) relation when using one to many (subclassed) - nhibernate

My scenario is that I have a User class, and that class has to be extended with related data, but without being subclassed.
For example the user might have lots of different profile data: AddressProfileData, FavoritesProfileData, etc etc.
I have decided to go with an abstract class and many implementations, kind of like in this post: inheritance mapping
However, I can't find a way to ensure (using nhibernate and not programmatically) that each item, for example AddressProfileData occurs only once per user.
Is this possible? If not, is there another solution for this problem which is more proper? I feel that sharing a common abstract class is building my app around NHibernate, and not the other way round.

AddressProfileData and FavoritesProfileData are likely to share almost nothing common, except for the fact that they both are extra information you attach to a User, so I don't think it makes sense to make them part of some inheritance hierarchy. Instead, I would go with something like this:
public class User
{
// ... other properties ...
public virtual AddressProfileData Address { get; set; }
public virtual FavoritesProfileData Favorites { get; set; }
}
public class AddressProfileData
{
// ... other properties ...
public virtual User User { get; set; }
}
<class name="User">
<!-- ... other properties ... -->
<one-to-one name="Address" property-ref="User" />
<one-to-one name="Favorites" property-ref="User" />
</class>
<class name="AddressProfileData">
<!-- ... other properties ... -->
<many-to-one name="User" column="User_id" unique="true" not-null="true" />
</class>
create table AddressProfileData (
/* ... other columns ... */
User_id int not null,
unique (User_id),
foreign key (User_id) references User (Id)
);
I'm sure you can imagine what FavoritesProfileData looks like.
With this setup, you ensure that each type of profile data only occurs once per user, and you also don't wind up in a weird place where you have to test which type of ProfileData you're dealing with before you can do anything with it. You always know exactly what kind of profile data you're touching.

Related

NHIbernate lazy load a parent object

I have two objects, Case and Note. A Case can have gobs of Notes, like, in the thousands. We are trying to load them asynchronously, in batches, and stream them to the UI so there is no delay waiting for them all to load.
The class/mappings are
public class Case
{
public virtual IList<Note> Notes { get; protected set; }
}
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="SCMS.TAMS.BusinessEntities" namespace="SCMS.TAMS.BusinessEntities">
<class name="Case" table="Cases">
<bag name="Notes" inverse="true" cascade="all" lazy="true">
<key column="CaseID" />
<one-to-many class="Note" />
</bag>
</class>
</hibernate-mapping>
public class Note
{
public virtual Case Case {get; set;}
public virtual long CaseId {get; set;}
}
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="SCMS.TAMS.BusinessEntities" namespace="SCMS.TAMS.BusinessEntities" default-lazy="true">
<class name="Note" table="CaseNotes">
<many-to-one name="Case" column="CaseID"/>
<property name="CaseId" column="CaseID" />
</class>
</hibernate-mapping>
Now, when I call
NHibernateSession.Query<Note>().Where(n => n.CaseId == 123).Skip(0).Take(10).ToList();
to load the first 10 Notes for Case 123, the thing loads the Case object, which takes about 30 seconds because there's lots of other things on it, and other logic when it gets loaded, etc., none of which I need/want at this time. All I want/need are the 10 Notes.
I've tried all sorts of variations on this mapping and none of them have worked. What am I doing wrong here?
How are you using this query? is it some thing for the UI? liking showing in a grid or something? or are you performing business logic in a component?
Either way you want to project into another object. Your query right now returns a list of notes which is then going to load that parent object per the mappings.
So if you are using this query to send the information to the UI of an asp.net mvc application, project directly into your view model
NHibernateSession.Query<Note>().Where(n => n.CaseId == 123).Select(n => new SomeViewModel { Prop1 = n.Prop1, Prop2 = n.Prop2 ...}).Skip(0).Take(10).ToList();
or create an anonymous object
NHibernateSession.Query<Note>().Where(n => n.CaseId == 123).Select n => new { n.Prop1, n.Prop2, ...}).Skip(0).Take(10).ToList();
This will keep the parent object from loading. It also has the added benefit that you are only querying the information you need because the query be limited to the data you are projecting.
Important to know is that if all above is true...
this is the real mapping (which is not it is just an obvious extract)
<class name="Note" table="CaseNotes">
<many-to-one name="Case" column="CaseID"/>
...
this is the class (again extract without ID)
public class Note
{
public virtual Case Case {get; set;}
public virtual long CaseId {get; set;}
}
and that would be a UNIT TEST statement to load notes:
var list = NHibernateSession
.Query<Note>()
.Where(n => n.CaseId == 123)
.Skip(0).Take(10)
.ToList();
then NHibernate will NEVER load the Case object. Never. Because:
NHibernate is lazy, just live with it
The reason, the trigger to load related reference (Case property) must be something explicit.
Mostly:
there is usage of the Case object somewhere. E.g. in override of the GetHashCode() the Case.ID is used
Or:
there is a serialization or DTO conversion which does touch the Case property
In those case, NHibernate must load that...
So, create few unit tests with basic queries and assure that your the is really as shown above. Then it will work as expected

NHibernate Many-to-one cascade

I have the below two classes:
public class Project
{
public virtual int ProjectId { get; set; }
public virtual string ProjectName { get; set; }
public virtual LegalEntity LegalEntity { get; set; }
}
and
public class LegalEntity
{
public virtual int LegalEntId { get; set; }
public virtual string Name { get; set; }
}
with mappings as:
<class name="Project" table="Project" dynamic-update="true">
<id name="ProjectId">
<generator class="native"/>
</id>
<property name="ProjectName" />
<many-to-one name="LegalEntity" column="LegalEntId" fetch="join" cascade="all-delete-orphan" />
</class>
and
<class name="LegalEntity" table="LegalEnt" dynamic-update="true">
<id name="LegalEntId">
<generator class="native"/>
</id>
<property name="Name" />
</class>
In database, Project table has a FK to LegalEntity's PK column. One Project will have only one legal entity. Different projects can have same legal entity. So thats the reason I have gone for many-to-one. Not sure if this is correct though.
Insert and update is working fine. But if I update a legal entity id in a project and that legal entity becomes orphan, I want it to be deleted. But its not happening. Am I wrong in understanding delete-all-orphan? If yes, how can I achieve this behaviour?
The many-to-one cascade does not support all-delete-orphan, see:
5.1.10. many-to-one
<many-to-one
...
cascade="all|none|save-update|delete" (4)
...
Also, it would be almost impossible to handle this feature by NHibernate's session. Because it does not have to be clear, that the referenced many-to-one is really orphan. There should be some farther checks in DB... there could be other places referencing this table row...
Suggestion: do it in your code as a part of the DAO or Business Facade implementation. Check if there are really no dependencies, and then issue explicit Delete()
EXTEND: Here is a QueryOver syntax to get a list of all "orphan" LegalEntity
// subquery
var subquery = QueryOver.Of<Project>()
.Select(x => x.LegalEntity.LegalEntId);
// just these legal entities, which are NOT used
var query = session.QueryOver<LegalEntity>()
.WithSubquery
.WhereProperty(y => y.LegalEntId)
.NotIn(subquery)
;
// orphans
var list = query
.List<LegalEntity>();
Now all-delete-orphan and delete-orphan have been implemented for many-to-one as you can see in this commit from Nov 19, 2014.
Those were not supported when the OP asked the questions or when Radim Köhler wrote his answer, but I think future visitors will appretiate the update.
The documentation is also updated and now says:
cascade="all|none|save-update|delete|delete-orphan|all-delete-orphan"
But the documentation is confusing now, because it still has the following note:
The cascade attribute permits the following values: all, save-update, delete, none.
So I've created a defect to fix that last part of the documentation.

One-to-many relationship not saved

I've got this relationship between a ReportRow (parent) and a Mark (child)
<class name="ReportRow">
<bag name="Marks" cascade="save-update" inverse="true">
<key column="ReportRowId"/>
<one-to-many class="Mark"/>
</bag>
</class>
// C# code
public virtual IList<Mark> Marks { get; set; }
But it's not being saved (in the Mark table, ReportRowId is always null).
I know these relationships always have to be bidirectional because of the NHibernate 'quirk' so for my Mark class I have:
<many-to-one name="ReportRow" class="ReportRow" column="ReportRowId" />
// C#
public virtual ReportRow ReportRow { get; set; }
I've even got some other examples of this kind of relationship working elsewhere in my project, but this one isn't working and I can't see any difference apart from...
... both Mark and ReportRow both have subclasses (e.g. ModuleMark and ModuleReportRow), which I'm using the joined-subclass strategy to implement the inheritance.
Would that have something to do with it? For both ends of the relationship, the mappings are defined in the parent class mapping rather than nested inside the <joined-subclass> mappings.
Thanks
How are you adding Marks to the collection? Because the collection is the inverse side of the relationship, you need to set the reference to the parent object on the child when adding it to the collection. A common approach is to use methods in the parent object to maintain the relationship, e.g.:
public void AddMark(Mark mark)
{
mark.ReportRow = this;
Marks.Add(mark); // better yet map the collection as a private field
}
I think of "inverse" as "who wears the pants in this relationship".

NHibernate: Composite key many-to-one mapping: Can't resolve property (foreign key component)

I hope anyone can help. I have to develop up against this third party database and I am kind of stuck with their crappy design. Still, I want to use NHibernate so I will have to jump through hoops.
Simplified, there is this "Event" table that has a relation a "Transportation" table. The transportation table has a composite primary key composed of the field "ID" and "FK_EventID", the latter of course referring back to the Event-record. Each event points to one distinct record in the transportation table, so it is a one-to-one relation really. Both fields are Guids BTW.
Attempting to map this out, this is how I created the classes (leaving out other data fields for simplicity's sake):
public class FcoEvent : IFcoObject
{
public virtual Guid ID { get; set; }
//public virtual Guid FK_TransportationID { get; set; } //ignore
public virtual FcoTransportation Transportation { get; set; }
And:
[Serializable]
public class FcoTransportation : IFcoObject
{
#region Members
public virtual Guid ID { get; set; }
public virtual Guid FK_EventID { get; set; }
In the mapping files I am attempting this (note that I am using many-to-one):
<class name="FcoLib.FcoEvent, FcoLib" table="FCO_Event">
<id name="ID" column="ID">
<generator class="guid" />
</id>
<many-to-one name="Transportation" not-found="ignore" cascade="save-update"
class="FcoLib.FcoTransportation, FcoLib">
<column name="FK_TransportationID" />
<column name="ID" />
</many-to-one>
And:
<class name="FcoLib.FcoTransportation, FcoLib" table="FCO_Transportation">
<composite-id>
<key-property name="ID" />
<key-property name="FK_EventID" />
</composite-id>
When I try to run this, I get the following exception message:
NHibernate.QueryException: could not resolve property: FK_TransportationID of: FcoLib.FcoEvent
My first hunch was that there may be a spelling error in the field name, but that didn't hold. So now I am completely puzzled and don't know how to proceed. Any help is greatly appreciated. Thnx.
Update
I think I found the source of the error. I had not looked there yet, because I assumed it was a mapping error, but apparently it is a querying error. It happens where I do the query:
fcoEvents = session.CreateCriteria(typeof(FcoEvent))
.Add(Restrictions.Eq("ID", eventId))
.Add(Restrictions.Eq("FK_TransportationID", transportId))
.List<FcoEvent>();
I will look further into this, but obviously I need to query this in a different way...
Silly me. I have been distracted by some faulty, outdated code. The point was to be able to retrieve the event including the related transport child using the primary key of the event and that simply works. Also it should be possible to retrieve the transport issue with the composite primary key and that can be accomplished with the below code.
public FcoTransportation GetTransportation(Guid transportId, Guid eventId)
{
FcoTransportation transport;
ISession session = Factory.OpenSession();
ITransaction tx = session.BeginTransaction();
try
{
transport = session.Get<FcoTransportation>(new FcoTransportation()
{
ID = transportId,
FK_EventID = eventId
});
So this has been a non-issue really. I have just been confused by the whole composite foreign key stuff. I hope I did not waste people's time.

How best to retrieve and update these objects in NHibernate?

I previously asked a question regarding modeling of a situation with Users, Items, and UserRatings. In my example UserRatings are associated with one User and one Item. A good answer was provided by Nathan Fisher and I've included the model he suggested below.
But I now have a question regarding retrieval of these objects.
The model links the entities by holding references to the entities.My question is, how best do I retrieve a particular UserRating to be updated? In this situation I would have the userID (from the asp.net auth session), and the itemID (from the URL). Also, there could be 1000s of ratings per user or item.
Back in the old school this would be as simple as one update query 'where x = userID and y=itemID. Easy. However the best way to accomplish this in NHibernate using a proper object model is not so clear.
A) I understand that I could create a repository method GetRatingByUserAndItem, and pass it both a User and Item object, which it would do an HQL/criteria query on to retrieve the Rating object. However to do this I assume that I would first need to retrieve User and the Item from the ORM before passing these back to the ORM in the query. I would then get the UserRating object, update it, and then have the ORM persist the changes. This seems ridiculously inefficent to me, compared to the old school method.
B) Maybe I could just new-up the UserRating object, and do a createorupdate type call the ORM (not sure on exact syntax). This would be better, but presumably I would still need to first retrieve the User and Item, which is still pretty inefficient.
C) Perhaps I should just retrieve the User (or the Item) from the ORM, and find the correct UserRating from its UserRatings collection. However, if I do that, how do I make sure that I'm not retrieving all of the UserRatings related to that User (or Item), but just the one related to the specific item and specific user?
D) It occured to me that I could just drop the full-blown references to User and Item from UserRating in the model, and instead have primitive references (UserID and ItemID). This would allow me to do something as simple as the oldschool method. Very tempting, but this just doesn't seem right to me - not very Object Oriented (and surely that's the main reason we are using an ORM in the first place!)
So, can anyone offer some sage advice? Am I on the right track with any of the options above? Or is there a better way that I have not considered?
Thanks in advace for your help! :)
UPDATE:
I've just posted a bounty for this, and understand this better, I would also like to know, using a similar approach, how best to perform the following queries:
Retrieve all the Items which a user had NOT rated.
Retrieve the Item(s) and Item rating(s) which the user had rated the lowest.
The Model follows below:
public class User
{
public virtual int UserId { get; set; }
public virtual string UserName { get; set; }
public virtual IList<UserRating> Ratings { get; set; }
}
public class Item
{
public virtual int ItemId { get; set; }
public virtual string ItemName { get; set; }
public virtual IList<UserRating> Ratings { get; set; }
}
public class UserRating
{
public virtual User User { get; set; }
public virtual Item Item { get; set; }
public virtual Int32 Rating { get; set; }
}
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Test" namespace="Test" >
<class name="User">
<id name="UserId" >
<generator class="native" />
</id>
<property name="UserName" />
<bag name="Ratings" generic="true" inverse="true" table="UserRating">
<key column="UserId" />
<one-to-many class="UserRating"/>
</bag>
</class>
<class name="Item" >
<id name="ItemId" >
<generator class="native" />
</id>
<property name="ItemName" />
<bag name="Ratings" generic="true" inverse="true" table="UserRating">
<key column="ItemId" />
<one-to-many class="UserRating"/>
</bag>
</class>
<class name="UserRating" >
<composite-id>
<key-many-to-one class="User" column="UserId" name="User" />
<key-many-to-one class="Item" column="ItemId" name="Item" />
</composite-id>
<property name="Rating" />
</class>
</hibernate-mapping>
Normally, when you use an ORM, you want to implement the business logic (say: changing data) object oriented. This requires to load the objects from the database. NH allows you to load them once, and change it without any reference to any database related stuff, but just changing property values.
This said, it is not always as easy as that. Sometimes there are performance reasons which requires other ways to update data.
You could use HQL updates or even SQL updates.
Another, more classical way to accomplish this is to only load UserRatings. This requires to make it an independent entity (it needs an id, avoid the composite id anyway, replcae it with many-to-one references). Then you filter the UserRatings by user and item, load the items you want to change in the database, and change them using object oriented programming.
It is always a trade-off between performance and object oriented programming. You should try to make it as OO as possible, and only do optimizations if it is needed. Maintainability is important.
I would avoid moving foreign keys to the domain model.
I would choose option C. Your concerns about performance indicate you may be optimizing prematurely. I think it would be fine for you to have a GetUser(int userId) method, then look for the appropriate item in its Ratings collection, and update it.
This does, however, bring up a common problem that ORMs suffer called the N+1 SELECT problem. Looking at each UserRating to find the appropriate one would likely result in one SELECT statement per UserRating. There are several ways to address this. One being to change your mapping file to either disable lazy loading of the Ratings collection, or else load it using 'join' fetching - see this section of the NHibernate documentation.
Using HQL your query would look like this
Select From UserRating
Where ItemId=: #ItemId
and UserId=: #UserId
This will give you the UserRating object that you are after then you can updated in and save it as necessary
And alternative would be
Session.CreateCriteria(typeof(ClassLibrary1.UserRating))
.Add(Expression.Sql(String.Format("ItemId={0}",UserId)))
.Add(Expression.Sql(String.Format("UserId={0}",ItemId)))
.List<ClassLibrary1.UserRating>();
This was the simplest way I could get this to work. I am not happy with the embedded strings but it works.
public void UpdateRating( int userId, int itemId, int newRating, ISession session )
{
using( var tx = session.BeginTransaction())
{
var ratingCriteria = session.CreateCriteria<UserRating>()
.CreateAlias( "Item" "item" )
.CreateAlias( "User" "user" )
.Add( Restrictions.Eq( "item.ItemId", itemId ) )
.Add( Restrictions.Eq( "user.UserId", userId ) );
var userRating = ratingCriteria.UniqueResult<UserRating>();
userRating.Rating = newRating;
tx.Commit();
}
}
You will need to test this as it has been a while since I used the criteria api but essentially what this does is creates alias for two association paths and then using those aliases, adds restrictions to the User and Item so that you only get the UserRating you are interested in.
Everything is done inside a transaction and by relying on NHibernate's dirty-state tracking, the change will be flushed to the database when the transaction commits.
Depending on the version of NHibernate you are using, you could also query using NHLinq or the NHLambda stuff that has been integrated and is now accessible via session.QueryOver<T>.
To retrieve a list of all the items that a user has not rated, you will need to use a subquery to identify all of the items the user has rated and then apply a not in clause to all of the items ( essentially get me all the items not in the items the user has rated ).
var ratedItemsCriteria = DetachedCriteria.For<UserRating>()
.CreateAlias( "Item" "item" )
.SetProjection( Projections.Property( "item.ItemId" ) )
.CreateCriteria( "User" )
.Add( Restrictions.Eq( "UserId", userId ) );
var unratedItemsCriteria = session.CreateCriteria<Item>()
.Add( Subqueries.PropertyNotIn( "ItemId", ratedItemsCriteria ) );
var unratedItems - unratedItemsCriteria.List<Item>();
In general, I think most of your problems could be resolved by judicious application of google, nhforge and the nhibernate user mailing list.
Query the database (using HQL, Criteria, SQL query, etc.) for the UserRating you want to modify
Change the UserRating however you like
Commit your changes
In pseudocode, this would look something like this:
using (ITransaction transaction = session.BeginTransaction())
{
UserRating userRating = userRatingRepository.GetUserRating(userId, itemId);
userRating.Rating = 5;
transaction.Commit();
}
This will involve two queries (as opposed to the one query "old school" solution). The first query (which happens in the GetUserRating call) will run a SQL "SELECT" to grab the UserRating from the database. The second query (which happens on transaction.Commit) will update the UserRating in the database.
GetUserRating (using Criteria) would look something like this:
public IList<UserRating> GetUserRating(int userId, int itemId)
{
session.CreateCriteria(typeof (UserRating))
.Add(Expression.Eq("UserId", userId))
.Add(Expression.Eq("ItemId", itemId))
.List<UserRating>();
}
I see that this Q has not been marked as answered, so I'll give it a shot. In my opinion, you have to look a lot at how the objects are used. It seems to me that you'd relate a UserRating more to an Item than to a user, simply because you'd display it next to the item in a UI. It doesn't feel that important to always display it for a user.
That is why I would remove the list of ratings from the user:
public class User
{
public virtual int UserId { get; set; }
public virtual string UserName { get; set; }
}
If you want the ratings for a user, you'd get that through a repository.
I'd leave the Item class unchanged, since you always want to see the ratings with an item:
public class Item
{
public virtual int ItemId { get; set; }
public virtual string ItemName { get; set; }
public virtual IList<UserRating> Ratings { get; set; }
}
The UserRating class could be completely disconnected from the Item and User classes. Just keep the ids in there, so you could retrieve an Item or User from a repository if you need to:
public class UserRating
{
public virtual int UserId { get; set; }
public virtual int ItemId { get; set; }
public virtual Int32 Rating { get; set; }
}