I just had a NHibernate related problem where I forgot to map one property of a class.
A very simplified example:
public class MyClass
{
public virtual int ID { get; set; }
public virtual string SomeText { get; set; }
public virtual int SomeNumber { get; set; }
}
...and the mapping file:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyAssembly"
namespace="MyAssembly.MyNamespace">
<class name="MyClass" table="SomeTable">
<property name="ID" />
<property name="SomeText" />
</class>
</hibernate-mapping>
In this simple example, you can see the problem at once:
there is a property named "SomeNumber" in the class, but not in the mapping file.
So NHibernate will not map it and it will always be zero.
The real class had a lot more properties, so the problem was not as easy to see and it took me quite some time to figure out why SomeNumber always returned zero even though I was 100% sure that the value in the database was != zero.
So, here is my question:
Is there some simple way to find this out via NHibernate?
Like a compiler warning when a class is mapped, but some of its properties are not.
Or some query that I can run that shows me unmapped properties in mapped classes...you get the idea.
(Plus, it would be nice if I could exclude some legacy columns that I really don't want mapped.)
EDIT:
Okay, I looked at everything you proposed and decided to go with the meta-data API...that looks the easiest to understand for me.
Now that I know what to search for, I found some examples which helped me to get started.
So far, I have this:
Type type = typeof(MyClass);
IClassMetadata meta = MySessionFactory.GetClassMetadata(type);
PropertyInfo[] infos = type.GetProperties();
foreach (PropertyInfo info in infos)
{
if (meta.PropertyNames.Contains(info.Name))
{
Console.WriteLine("{0} is mapped!", info.Name);
}
else
{
Console.WriteLine("{0} is not mapped!", info.Name);
}
}
It nearly works, except one thing:
IClassMetadata.PropertyNames returns the names of all the properties except the ID.
To get the ID, I have to use IClassMetadata.IdentifierPropertyName.
Yes, I could save .PropertyNames in a new array, add .IdentifierPropertyName to it and search that array.
But this looks strange to me.
Is there no better way to get all mapped properties including the ID?
You could use the NHibernate meta-data API to find the mapped properties, and reflection to find all the properties.
Edit No, there isn't any other way list all the properties including the id. It isn't that hard to use:
foreach (PropertyInfo info in infos)
{
if (meta.PropertyNames.Contains(info.Name) || info.Name = meta.IdentifierPropertyName)
{
Console.WriteLine("{0} is mapped!", info.Name);
}
else
{
Console.WriteLine("{0} is not mapped!", info.Name);
}
}
There are two tools I'm aware of that can help with this:
Fluent NHibernate persistence specification testing
Nhibernate Ghostbuster
but they don't specifically address the problem you had with an unmapped property. The best solution is to write good unit tests that ensure that the properties you want to persist are persisted correctly. It's tedious but necessary.
Related
In a few cases, I have a property that needs a "backing property" for practical reasons.
For example, I have one type with a Name property - there is no transformation of the value happening on access, it merely triggers an action of some kind; a side-effect, if you will. (not that it matters for the sake of discussion, but in this particular case, the name gets copied somewhere else when changed.)
Let's say:
public class Person
{
private string __name;
protected internal virtual string _name
{
get
{
return this.__name;
}
set
{
this.__name = value;
}
}
public virtual string Name
{
get
{
return _name;
}
set
{
_name = value;
// action when changing the name takes place here...
}
}
}
So the "_name" property is mapped to the database, but is kept protected/internal so that it cannot be modified directly. And the second public property "Name" provides the actual access.
The reason I have it set up this way, is because if that action was built directly into the mapped "_name" property's set-method, it would be triggered when an object is hydrated from the database, which is not what I want.
This all works fine as such.
The problem is, when you need to query this type, attempting to query Person.Name won't work, because that property isn't mapped!
What I dislike about this, is the fact that you're writing code against Person.Name, but have to write queries against Person._name, which is error-prone and confusing.
Is there a better way to solve this problem?
Can you use nosetter.camelcase-underscore for the access in the mapping? This would set the field directly (if named correctly, eg _name) instead of using the property setter.
eg:
<property name="Name" column="Name" type="String" access="nosetter.camelcase-underscore"/>
I have two related objects: ProgramSession and ProgramTask, with a one-to-many relationship. A ProgramSession has many ProgramTasks. So the objects looks like this:
public class ProgramSession
{
public virtual IList<ProgramTask> ProgramTasks
{
get { return _programTasks; }
set { _programTasks = value; }
}
}
public class ProgramTask
{
public virtual ProgramSession ProgramSession
{
get { return _programSession; }
set { _programSession = value; }
}
}
And the mappings...
ProgramSession.hbm.xml
<bag name="ProgramTasks" lazy="false" cascade="all-delete-orphan" inverse="true" >
<key column="SessionUid"></key>
<one-to-many class="ProgramTask"></one-to-many>
</bag>
ProgramTask.hbm.xml
<many-to-one name="ProgramSession" column="SessionUid" class="ProgramSession" />
The problems begin when I try to change the ProgramSession of a ProgramTask.
If I remove the ProgramTask from the ProgramSession.ProgramTasks list property of the old session, and then add it to the same property on the new session, NHibernate tells me that the a deleted object will be resaved.
If I simply change the value of the ProgramTask.ProgramSession object, I have no problem saving. However, I get weird behavior if I do not save immediately because the ProgramSession.ProgramTasks properties (on both sessions) are not synchronized until after the NHibernate session is refreshed.
Changing the ProgramTask.ProgramSession object without directly modifying the lists as well, creates an invalid state. Take the following code as an example:
programTask.ProgramSession = newProgramSession;
Assert.That(newProgramSession.ProgramTasks.Contains(programTask)); // fails
Assert.That(!oldProgramSession.ProgramTasks.Contains(programTask)); // fails
This is more problematic in code that executed later on, that assumes the ProgramTasks collections are synchronized with the ProgramSession property. For example:
foreach(var programTask in programSession.ProgramTasks)
{
// whatever
}
One hack I used to work around this was to query the list. I can't use it everywhere, and it's clearly a bad solution, but it underscores the problem:
var tasksActuallyInSession =
programSession.ProgramTasks
.Where(task => task.ProgramSession == programSession)
.ToList();
Is there any way to handle this kind of situation? A best practice? Am I doing something wrong? Is the mapping incorrect? Is there some super-secret NHibernate flag I need to set?
Not sure if I understand everything what you are doing here. Some thoughts:
If you decide to move ProgramTasks around, then they get independent and should not be mapped using cascade="all-delete-orphan". If you do this, NH removes the ProgramTask from the database when you remove it from a ProgramSession.
Map it using cascade="none" and control the lifecycle of the object yourself. (this means: store it before a ProgramSession gets stored. Delete it when it is not used anymore.)
Not sure if this this is also a problem, but note that if you have a inverse reference, you code is responsible to make the references consistent. Of course, references get cleaned up after storing to the database and loading into an empty session, this is because there is only one foreign key in the database. But this is not the way it should be done. It is not responsibility of NH to manage your references. (Its only responsibility is to persist what you are doing in memory.) So you need to make it consistent in memory, and implement your business logic as if there wasn't NHibernate behind.
nHibernate is giving the error : Custom type does not implement UserCollectionType: myApp.Domain.OrderLineCollection.
BindingList implements IList, so why is nHibernate trying to use UserCollectionType instead of IList?
public class OrderHeader
{
public virtual int OrderHeaderId { get; set; }
public virtual string OrderNumber { get; set; }
public virtual OrderLineCollection Line { get; set; }
}
public class OrderLineCollection : BindingList<OrderHeader> { }
public class OrderHeaderMap : ClassMap<OrderHeader>
{
public OrderHeaderMap()
{
WithTable("Orders");
Id(x => x.OrderHeaderId, "OrderId").GeneratedBy.Identity();
Map(x => x.OrderNumber);
HasMany(x => x.Line).WithKeyColumn("OrderHeaderId").AsList();
}
}
<list name="Line">
<key column="OrderHeaderId" />
<index />
<one-to-many class="myApp.Domain.OrderLine, myApp.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</list>
NHibernate has it's own custom typed list which implements IList underneath.
I'm afraid you won't be able to use yours without creating nHibernate UserType.
But i might be wrong and would be glad to hear why. :)
You might want to check the XML that's created by fluentNHibernate - it's quite possible they take the type of the Line property and set it explicitly.
This should work if you don't set the type explicitly. I tried implementing a custom collection deriving from IList - and it worked when I didn't specify the type on the bag/list whatever in the mapping.
Ok, I did a quick test Arnis L. is right - it probably won't work without implementing UserCollectionType. In my experience, it's a pain to implement .
(somehow I remembered doing something like this but I guess my mind's playing tricks on me)
I look at the NHibernate source code and at least for PersistentBag and PersistentList NHibernate will instanciate a ArrayList object as the back end list, not a OrderLineCollection as one could thought. When you implement IUserColletionType there is a method who tells NHibernate what collection it should create, and also what Persistent collection Hibernate should use to sav. Take a look at this link might help a lot. But I still cant do Nhibernate work with BindingList.
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; }
}
I have the following class:
public class MyClass
{
private List<long> _myList = new List<long>();
public virtual string MyID { get; set; }
public virtual string MyData
{
get
{
return SomeStaticClass.Serialize(_myList);
}
set
{
_myList = SomeStaticClass.Deserialize<List<long>>(value);
}
}
public virtual List<long> MyList
{
get { return _myList; }
}
}
And the following mapping file:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyNamespace"
namespace="MyNamespace">
<class name="MyNamespace.MyClass" table="MY_TABLE">
<id name="MyID" column="MY_ID" type="System.String">
<generator class="assigned"></generator>
</id>
<property name="MyData" column="MY_DATA"></property>
</class>
</hibernate-mapping>
When I try to run the following line:
session.Delete("From MyClass m");
I am getting a QuerySyntaxException with the message "MyClass is not mapped [From MyClass s]".
When I change the name of the "MyID" field to "ID" in the mapping file, the exception becomes
NHibernate.PropertyNotFoundException: Could not find a getter for property 'ID' in class 'MyNamespace.MyClass'.
so I am assuming it can find the mapping file. I made sure that the mapping file is an embedded resource, checked and dobule checked the namespace and class names in the mapping file. What may cause the error? I think it may relate to the MyList property which is not mapped but I am not sure since I am using non-mapped properties on my other classes without a problem.
EDIT: I tried overriding this class, with a class which has no "MyData" property and redefining "MyList" property as string. I am still receiving the same error for my overridden class.
EDIT 2: Tried with a very simple class with the same property names with same return types and only simple get; set; blocks. I still get the same error. I am almost sure that nhibernate can see my mapping files because if I change the name of a single property, it gives me PropertyNotFound instead of "class in not mapped".
How are you loading the hbms? If they are resources, make sure you've actually set the files to be embedded resources in Visual Studio
In case of mapping
<class name="MyClass" table="MY_TABLE">
you should use, for example:
session.CreateQuery("from MyClass")
but not:
session.CreateQuery("from MY_TABLE")
what about if you use
session.Delete("From MyNamespace.MyClass m");
I was just looking at the HQL reference and noticed in their cat example they use fully qualified objects, i.e. Eg.Cat.
You should set the related *.hbm.xml as Embedded Resource.
Make sure your "Build Action of the file" is "Embedded Resource".
I had this problem. I forgot to put hbm in the name of mapping XML files.
it seems a bit strange you are specifying the namespace twice in the mapping file. I would try just specifying the name attribute as just "MyClass" instead of "MyNamespace.MyClass" so it would be
<class name="MyClass" table="MY_TABLE">
I had a similar problem like this. Basically, I included a new project into the solution and I did not map the namespace in the hibernate.cfg.xml file.