I have a data structure which uses composite ids (Which I dont wish to change to single)
Everything loads fine except for many-to-one joins which if the join is empty, instead of mapping the property to null, maps it to an empty proxy object. I have written an ugly work around( see bleow). Any solutions to this?
private Node _Parent;
public Node Parent
{
get
{
return this._Parent;
}
set
{
this._Parent = Proxy.Check<Node>(value);
}
}
internal static class Proxy
{
public static T Check<T>(T obj) where T : PersistentObject
{
if (obj is NHibernate.Proxy.INHibernateProxy && obj != null)
{
try
{
int id = obj.ID;
return obj;
}
catch //Proxy only object cant retrieve ID
{
return null;
}
}
else
{
return obj;
}
}
}
with the mapping file beginning
<class name="Node" table="Node">
<composite-id>
<key-property name="ID"/>
<key-property name="VersionID"/>
</composite-id>
and accessed by
<many-to-one name="Node" class="Node" >
<column name="NodeID"/>
<column name="VersionID" />
</many-to-one>
Not exactly sure if this is the perfect fix for this situation, but this fixed the issue for me when I encountered the same problem while working on an old DB with composite keys.
By setting not-found to ignore on your links, NHibernate will treat empty objects as null instead of exceptions. When using this technique NHibernate will execute a seperate query so there may be small performance hits, as this is basically eager loading the object.
You could try just eager loading the object instead of using this technique, but I have a feeling it would return an exception as it would be expecting an object (not null). I would suggest posting a question in the NHibernate forums if this doesn't work as I am definately not an expert in this area, but this may be a smaller/less ugly work around for you.
For example:
<many-to-one name="Node" class="Node" not-found="ignore">
<column name="NodeID"/>
<column name="VersionID" />
</many-to-one>
Hope this helps,
Jay
Related
I have many NH entities that use non generic list. (using generic="false" and IList)
I have downloaded NH4 and noticed that NH4 does not support persisting non generic list. While saving I got this exception :
Unable to cast object of type 'System.Collections.ArrayList' to type
'System.Collections.Generic.IEnumerable`1[System.Object]'.
Is there any simple solution to upgrade my NH entities?
public virtual IList TemplateProperties
{
get
{
return this._TemplateProperties;
}
set
{
this._TemplateProperties = value;
}
}
<bag name="TemplateProperties" generic="false">
<key>
<column name="PRPT_ID" not-null="true" precision="10" scale="0" sql-type="int" />
</key>
<one-to-many class="TemplateProperty" />
</bag>
change the implementation of the models to use generic lists. They are still handed out as IList
private readonly List<object>_TemplateProperties = new List<object>();
public virtual IList TemplateProperties
{
get { return this._TemplateProperties; }
}
Then specify in the mapping to use the field instead of the property access the value
<bag name="TemplateProperties" access="field.pascalcase-underscore">
i have old legacy DB which has dead links in their tables. I have class mapped in nhibernate like this:
<class name="Visible" table="table_visible">
<composite-id>
<key-many-to-one column="object_id" name="ObjectA" />
<key-many-to-one column="sub_object_id" name="SubObject" />
</composite-id>
<property column="visible" name="VisibleRow" />
</class>
and:
public class Visible
{
public virtual ObjectAClass ObjectA { get; set; }
public virtual SubObjectClass SubObject { get; set; }
public virtual bool VisibleRow { get; set; }
public override bool Equals(object obj)
{
var other = ((Visible)obj);
return this.ObjectA.Equals(other.ObjectA) && this.SubObject.Equals(other.SubObject);
}
public override int GetHashCode()
{
return this.ObjectA.GetHashCode() + (this.SubObject != null? this.SubObject.GetHashCode(): 0);
}
}
Now all works fine when all joins in database are correct, but when i find such sub_object_id which doesnt have entity, nhibernate throws me error
No row with the given identifier exists:[SubObject#123]
Is there a way to map composite key so that when its subentity is not found, the whole entity wouldnt be loaded (like with inner join)?
NHibernate v2.0.50727
Following Daniel Schilling idea of fetch Visible entities with a where exists sub-query, found that there is loader element available in mappings.
<class name="ObjectA" table="table_object">
.........
<set name="VisibleList" cascade="all" lazy="false" inverse="true">
<key column="object_id" />
<one-to-many class="Visible" />
<loader query-ref="valid_entities"/>
</set>
</class>
<sql-query name="valid_entities">
<load-collection alias="v" role="ObjectA.VisibleList"/>
SELECT {v.*}
FROM table_visible v
INNER JOIN table_sub_entities e ON e.sub_entity_id=v.sub_entity_id
WHERE v.object_id=?
</sql-query>
And nothing else needed to be changed.
<key-many-to-one column="sub_object_id" name="SubObject" not-found="ignore" />
... may be helpful. From the NHibernate Documentation...
ignore will treat a missing row as a null association
Please be aware of the performance penalty associated with using this option. Whenever NHibernate fetches a Visible entity, it will also have to fetch SubObject. If you don't go ahead and fetch it in your query, this means that NHibernate will be issuing lots of lazy loads.
This doesn't meet your "when its sub-entity is not found, the whole entity wouldn't be loaded" goal. Instead NHibernate would give you an entity with a null sub-entity. If you want that inner-join-like behavior, then I think you would need to fetch your Visible entities with a where exists sub-query to make sure the SubObject actually exists.
The best option would be to fix the data in the database and add a foreign key constraint.
I just ran across this: Relations with not-found="ignore". I promise I'm not copying Ricci's content - I'm writing this from my own experience.
Consider this class that represents a node in a hierarchical structure:
public class Node
{
public Node()
{
Children = new List<Node>();
}
public virtual int Id { get; set; }
public virtual IList<Node> Children { get; set; }
public virtual Node Parent { get; set; }
public virtual int Position
{
get { return Parent == null ? -1 : Parent.Children.IndexOf(this); }
set { }
}
}
The mapping looks like this (as NHibernate does not support lists in bidirectional associations, I use a bag here and have the children determine their position automatically):
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" namespace="AmbiguousColumn" assembly="AmbiguousColumn" xmlns="urn:nhibernate-mapping-2.2">
<class name="Node">
<id name="Id" type="Int32">
<generator class="identity" />
</id>
<bag name="Children" inverse="true" cascade="all" order-by="Position">
<key column="Parent" />
<one-to-many class="Node" />
</bag>
<many-to-one name="Parent" />
<property name="Position" />
</class>
</hibernate-mapping>
To get all nodes with their children loaded I'd use a query like this:
var nodes = session.QueryOver<Node>()
.Fetch(x => x.Children).Eager
.List();
However, executing this results in an exception:
NHibernate.Exceptions.GenericADOException: could not execute query
[...(sql)...] ---> System.Data.SqlClient.SqlException: Ambiguous column name 'Position'.
The SQL:
SELECT
this_.Id as Id0_1_,
this_.Parent as Parent0_1_,
this_.Position as Position0_1_,
children2_.Parent as Parent3_,
children2_.Id as Id3_,
children2_.Id as Id0_0_,
children2_.Parent as Parent0_0_,
children2_.Position as Position0_0_
FROM
Node this_
left outer join
Node children2_
on this_.Id=children2_.Parent
ORDER BY
Position
I understand why this happens: NH joins the same table twice, but uses the order clause without qualifying the column name.
The question is: how can I make this scenario work? Resorting to instead of is probably difficult as I'd like to have a bidirectional relation.
There are a couple of similar question on SO, but nowhere did I find an actual solution.
Update: the error is database/driver specific. Using the Sql Server CE (e.g. SqlServerCeDriver and MsSqlCe40Dialect) I get the proper query. Using Sql Server (e.g. Sql2008ClientDriver and MsSql2012Dialect) produces the unqualified queries.
According to my own tests, this behavior still exists in the master branch on github.
A gist with a test case: https://gist.github.com/anonymous/5377535
I think I found the cause of the problem and viable workarounds:
The cause of the issue is the fact that the column is called "Position", which is a reserved word in ODBC according to http://msdn.microsoft.com/en-us/library/ms189822.aspx
This combined with the fact that the default value for NH's hbm2ddl.keywords property is set to "keywords" somehow caused NH not to qualify the order-by clause, probably because it though "Position" was a keyword, not a column.
http://nhforge.org/blogs/nhibernate/archive/2009/06/24/auto-quote-table-column-names.aspx
Ways to fix it:
1) Use a different name for the property - one that isn't a keyword. In this case, PositionInParent would have worked without any issues.
2) Quote the order by clause properly using back-ticks.
<bag name="Children" inverse="true" cascade="all" order-by="`Position`">
Or whatever it takes in your mapping API of choice, e.g. in mapping by code:
cls.Bag(x => x.Children,
map =>
{
map.Inverse(true);
map.Cascade(Cascade.All);
map.Key(key => key.Column("Parent"));
map.OrderBy("`Position`"); // note that you must not use a lambda expression in this case
},
map => map.OneToMany());
3) Disable keyword auto import, ie. set hbm2ddl.keywords to none (neither keywords nor auto-quote will work):
<property name="hbm2ddl.keywords">none</property>
Or programmatically:
config.DataBaseIntegration(db => db.KeywordsAutoImport = Hbm2DDLKeyWords.None);
You can still auto-quote reserved words by calling SchemaMetadataUpdater.QuoteTableAndColumns just before building the session factory.
SchemaMetadataUpdater.QuoteTableAndColumns(config);
I'll stick with approach 3 for now as it is the most painless so far.
I'm trying to write an audit tracking for Nhibernate that hooks into the PreUpdate event. I have an AuditLogEntry class (when, who, etc), that contains a list of AuditLogEntryDetails (i.e. individual properties that changed). If I isolate the AuditLogEntry class from the entity being audited then my code runs with no errors. However, if I add a list of AuditLogEntry's to the entity being audited then my code throws a
collection [DomainObjects.AuditTracking.AuditLogEntry.Details] was not processed by
flush()
assertion failure when I attempt to save the modified list inside the event listener. This only happens when the audited item already has one (or more) AuditLogEntry instance in the list. If there are no entries then a new list is created and added to the entity being audited and this is fine.
I think by isolating the issue to the above it would appear to be around (lazy) loading the existing list to add the new instance of AuditLogEntry too. However I've been unable to progress any further. Adding 'Lazy="False"' to the list mapping doesn't appear to help. I'm really in the early days of using NHibernate, having borrowed concepts from both the HN 3.0 Cookbook and this blog post. My code is very similar to this, but attempts to add the audit history to the item being audited in a list (and as such I think that I need to also do that in the pre, rather than post update event).
A snap shot of the entity interfaces/classes in question are:
public class AuditLogEntry : Entity
{
public virtual AuditEntryTypeEnum AuditEntryType { get; set; }
public virtual string EntityFullName { get; set; }
public virtual string EntityShortName { get; set; }
public virtual string Username { get; set; }
public virtual DateTime When { get; set; }
public virtual IList<AuditLogEntryDetail> Details { get; set; }
}
public interface IAuditTrackedEntity
{
Guid Id { get; }
IList<AuditLogEntry> ChangeHistory { get; set; }
}
public class AuditTrackedEntity : StampedEntity, IAuditTrackedEntity
{
public virtual IList<AuditLogEntry> ChangeHistory { get; set; }
}
public class LookupValue : AuditTrackedEntity
{
public virtual string Description { get; set; }
}
For the mappings I have:
AuditTrackedEntry.hbm.xml:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DomainObjects" namespace="DomainObjects.AuditTracking">
<class name="AuditLogEntry">
<id name="Id">
<generator class="guid.comb" />
</id>
<version name="Version" />
<property name="AuditEntryType"/>
<property name="EntityFullName"/>
<property name="EntityShortName"/>
<property name="Username"/>
<property name="When" column="`When`"/>
<list name ="Details" cascade="all">
<key column="AuditLogEntryId"/>
<list-index column="DetailsIndex" base="1"/>
<one-to-many class="AuditLogEntryDetail"/>
</list>
</class>
</hibernate-mapping>
lookupvalue.hbm.xml:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DomainObjects" namespace="DomainObjects">
<class name="LookupValue">
<id name="Id">
<generator class="guid.comb" />
</id>
<discriminator type="string">
<column name="LookupValueType" unique-key="UQ_TypeName" not-null="true" />
</discriminator>
<version name="Version" />
<property name="Description" unique-key="UQ_TypeName" not-null="true" />
<property name="CreatedBy" />
<property name="WhenCreated" />
<property name="ChangedBy" />
<property name="WhenChanged" />
<list name ="ChangeHistory">
<key column="EntityId"/>
<list-index column="ChangeIndex" base="1"/>
<one-to-many class="DomainObjects.AuditTracking.AuditLogEntry"/>
</list>
</class>
</hibernate-mapping>
The EventListener PreUpdate event handler calls the follow code:
The lines that cause the problem are commented near the end of the code block
public void TrackPreUpdate(IAuditTrackedEntity entity, object[] oldState, object[] state, IEntityPersister persister, IEventSource eventSource)
{
if (entity == null || entity is AuditLogEntry)
return;
var entityFullName = entity.GetType().FullName;
if (oldState == null)
{
throw new ArgumentNullException("No old state available for entity type '" + entityFullName +
"'. Make sure you're loading it into Session before modifying and saving it.");
}
var dirtyFieldIndexes = persister.FindDirty(state, oldState, entity, eventSource);
var session = eventSource.GetSession(EntityMode.Poco);
AuditLogEntry auditLogEntry = null;
foreach (var dirtyFieldIndex in dirtyFieldIndexes)
{
if (IsIngoredProperty(persister, dirtyFieldIndex))
continue;
var oldValue = GetStringValueFromStateArray(oldState, dirtyFieldIndex);
var newValue = GetStringValueFromStateArray(state, dirtyFieldIndex);
if (oldValue == newValue)
{
continue;
}
if (auditLogEntry == null)
{
auditLogEntry = new AuditLogEntry
{
AuditEntryType = AuditEntryTypeEnum.Update,
EntityShortName = entity.GetType().Name,
EntityFullName = entityFullName,
Username = Environment.UserName,
//EntityId = entity.Id,
When = DateTime.Now,
Details = new List<AuditLogEntryDetail>()
};
//**********************
// The next three lines cause a problem when included,
// collection [] was not processed by flush()
//**********************
if (entity.ChangeHistory == null)
entity.ChangeHistory = new List<AuditLogEntry>();
entity.ChangeHistory.Add(auditLogEntry);
session.Save(auditLogEntry);
}
var detail = new AuditLogEntryDetail
{
//AuditLogEntryId = auditLogEntry.Id,
PropertyName = persister.PropertyNames[dirtyFieldIndex],
OldValue = oldValue,
NewValue = newValue
};
session.Save(detail);
auditLogEntry.Details.Add(detail);
}
session.Flush();
}
As previously stated, in this configuration I get an assertion failure "collection [] was not processed by flush()". If I remove the three lines above and the list mapping in the lookupcode.hmb.xml then everything works as expected, other than the entity being audited no longer contains a reference to it's own audited items.
we were facing very similar problem, exactly same exception, but in different situation. No solution found yet...
We have NH event listener implementing IPreUpdateEventListener and OnPreUpdate method used for audit log. Everything is fine for simple properties updating, dirty checking works well, but there are problems with lazy collections. When updating some object which has lazy collection and accessing any object field in the event listener OnPreUpdate method, the same exception as mentioned above is thrown. When lazy set to false, problem disappears.
So it seems there is some problem with lazy collections (and no influence of collection initialization before saving). Our problem isn't connected with creating new collection items; only reading existing object, only its field accessing from the event listener causes the problem.
So in your case, maybe, lazy set to false only for the associatioon could fix the problem, but on the other hand probably you really want to have the collection to be lazy. So hard to say, if the problem has resolution or IInterceptor have to be used instead.
Ok, I have found your problem, this line is actually causing the problem.
Details = new List<AuditLogEntryDetail>()
You can't initialize an empty collection before you save because the EntityPersister will not persist the collection, but it will error that the collection has not been processed.
Also, once nHibernate calls event listeners, cascades do not work (not sure if this is by design or not). So even though you are adding the detail item to the collection later, you are only calling save on the detail, not the parent, so the change is not propagated. I would recommend re-factoring so that items are completed in this order...
Detail, then save,
AuditLogEntry, then save,
Entity, then update.
I had exactly same problem while using EventListener. I was looping through properties one-by-one to detect changes, that included enumerating collections. However when I added a check for the collection using NHibernateUtil.IsInitialized(collection), problem disappeared. I wouldn't catch-and-ignore the AssertionFailure exception since it might have unknown side-effects.
There's an issue still open to solve this problem. There's a patch at the end of topic that solved it to me.
https://nhibernate.jira.com/browse/NH-3226
Can someone explain this little mystery to me about how NHibernate handles composite elements.
I have classes that look like this;
public class Blog
{
public virtual int Id
{
get;
private set;
}
public virtual ISet<Comment> Comments
{
get;
set;
}
}
public class Comment
{
public virtual string CommentText
{
get;
set;
}
public virtual DateTime Date
{
get;
set;
}
}
and mappings like this;
<class name="Blog" table="blog">
<id name="Id" column="id" unsaved-value="0">
<generator class="hilo"/>
</id>
<set name="Comments" table="blog_comments">
<key column="blog_id" />
<composite-element class="Comment">
<property name="CommentText" column="comment" not-null="true" />
<property name="Date" column="date" not-null="true" />
</composite-element>
</set>
</class>
However when i perform a select like this;
using (ITransaction transaction = session.BeginTransaction())
{
Blog blog = session.CreateCriteria(typeof(Blog))
.SetFetchMode("Comments", FetchMode.Eager)
.Add(Expression.IdEq(2345))
.UniqueResult();
transaction.Commit();
}
NHibernate issues a select with a join to get the blog with posts BUT then deletes all comments and then inserts the comments! Why is it doing this? If i do not use a transaction then it will ONLY perform the select and not the DELETE and INSERT as I would expect. What am I missing? I am using NHibernate 2.0
I think you need to override Equals() and GetHashCode() on Comment. NHibernate doesn't have an ID to go on for entity equality so you have to define what makes a comment entity equal to another comment.
Could be wrong :)
Edit
From nhibernate.info (8.2)
Note: if you define an ISet of composite elements, it is very important to implement Equals() and GetHashCode() correctly.
And an example of implementing Equals / GetHashCode from nhibernate.info (4.3)
public class Cat
{
...
public override bool Equals(object other)
{
if (this == other) return true;
Cat cat = other as Cat;
if (cat == null) return false; // null or not a cat
if (Name != cat.Name) return false;
if (!Birthday.Equals(cat.Birthday)) return false;
return true;
}
public override int GetHashCode()
{
unchecked
{
int result;
result = Name.GetHashCode();
result = 29 * result + Birthday.GetHashCode();
return result;
}
}
}
My question would be why you are committing if you only need to do a select? I believe the reason it's deleting all the comments is that when you call commit on the transaction, the blog object and it's associated comments are cached in the session that is used to create the transaction. When you call the commit, you are causing all the objects in the session to be saved which is causing the save back to the database. I'm not clear on why it's deleting the comments but it is correct behaviour to save the objects.
I also stumbled upon this today:
NHibernate is deleting my entire
collection and recreating it instead
of updating the table.
This generally happens when NHibernate
can't figure out which items changed
in the collection. Common causes are:
replacing a persistent collection entirely with a new collection instance
passing NHibernate a manually constructed object and calling Update on it.
serializing/deserializing a persistent collection apparently also causes this problem.
updating a with inverse="false" - in this case, NHibernate can't construct SQL to update an individual collection item.
Thus, to avoid the problem:
pass the same collection instance that you got from NHibernate back to it (not necessarily in the same session),
try using some other collection instead of ( or ), or
try using inverse="true" attribute for .