I've either misunderstood the NHibernate manual or I've done something wrong. Can anyone help?
I am trying to retrieve a User without the AuditLogEntrys.
But NHibernate is still loading the AuditLogEntrys. I only want the AuditLogEntrys loaded when I access the property.
public class User
{
public virtual int UserId { get; set; }
public virtual string UserName { get; set; }
public virtual IList<AuditLogEntry> AuditLogEntrys { get; set; }
}
public class AuditLogEntry
{
public virtual int Id { get; set; }
public virtual DateTime DateRead { get; set; }
public virtual string MachineName { get; set; }
}
Mappings:
<class name="Model.User, Model"
table="User"
lazy="true">
<id name="UserId" access="property" column="UserID">
<generator class="native"></generator>
</id>
<property name="UserName" access="property" />
<bag name="AuditLogEntrys" lazy="true" access="property">
<key column="UserID" />
<one-to-many class="Model.AuditLogEntry, Model"></one-to-many>
</bag>
<class name="Model.AuditLogEntry, Model"
table="AuditLog"
lazy="true">
<id name="Id" access="property" column="ID">
<generator class="native"></generator>
</id>
<property name="DateRead" access="property" column="DateRead"></property>
<property name="MachineName" access="property" column="MachineName"></property>
</class>
Code to get the user:
public IList<User> GetUserByUserName(string userName)
{
ICriteria criteria = NHibernateSession.CreateCriteria(typeof(User))
.Add(Expression.Eq("UserName", userName));
return GetByCriteria(criteria);
}
Now I'd expected a User object with an empty collection of AuditLogEntry's, but this is not what is happening.
Any ideas??
Thanks.
With lazy loading, you will get a populated list of objects, but they are not yet "hydrated" from the database. The lazily-loaded objects are not your entity types, but are instead "proxy objects" which will be populated/hydrated with real data when you access the items in the collection.
The use of proxy objects is the reason why you have to make all of your properties virtual in your entity types. The Proxy types are dynamically-generated subclasses of your entity type, which make the actual calls to the database when you access the properties.
Hopefully I understood your question, but the difference is that you get actual objects back, not an empty list. If you get back an empty list, it means that there were no AuditLogEntry items referencing your User in the database.
Related
Problem encountered
When I create a transient instance with a children collection, everything gets persisted.
Aside, if I update an instance of one of the children object, it doesn't get updated when I save the parent object.
I'm actually using cascade="all"
Problem reproduction
The problem occurs when I have loaded all of my Customer occurences, and I change an invoice, though I always use the same ISession.
var repository = new CustomerRepository(session);
var customers = repository.GetAll();
var customer = customers.Where(c => c.Name == "Stack Overflow").FirstOrDefault();
customer.Invoices
.Where(i => i.Number == "1234")
.Approve(WindowsIdentity.GetCurrent().Name);
repository.Save(customer);
Step by step debugging clearly shows the repository.Save() method being executed, and the changes won't show inte the underlying database.
An update directly against the database table is possible, so no contraint causing the update to fail on the database-side.
Herewith some code in case it might help.
Customer.hbm.xml
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="MyProject.Model"
assembly="MyProject">
<class name="Customer" table="AC_CUST" schema="AC">
<id name="Id" column="AC_CUST_ID" type="Int32" unsaved-value="0">
<generator class="sequence-identity">
<param name="sequence">AC_CUST_ID_SEQ</param>
<param name="schema">AC</param>
</generator>
</id>
<property name="Name" column="AC_CUST_NAME" type="String"
not-null="true" />
<property name="PhoneNumber" column="AC_CUST_PHNUM" type="Int64"
not-null="true" />
<bag name="Invoices" table="ESO_RAPP_ACCES_INFO_DSQ" schema="AC"
fetch="join" lazy="true" inverse="true" cascade="all">
<key column="AC_CUST_ID" foreign-key="AC_CUST_INV_FK" />
<one-to-many class="Invoice" />
</bag>
</class>
</hibernate-mapping>
Customer
public class Customer {
public Customer() { Invoices = new List<Invoice>(); }
public virtual int Id { get; proected set; }
public virtual IList<Invoice> Invoices { get; protected set; }
public virtual string Name { get; set; }
public virtual string PhoneNumber { get; set; }
}
Invoice.hbm.xml
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="MyProject.Model"
assembly="MyProject">
<class name="Invoice" table="AC_INV" schema="AC">
<id name="Id" column="AC_INV_ID" type="Int32" unsaved-value="0">
<generator class="sequence-identity">
<param name="sequence">AC_INV_ID_SEQ</param>
<param name="schema">AC</param>
</generator>
</id>
<property name="Approved" column="AC_INV_APPRD" type="DateTime"
not-null="false" />
<property name="Approver" column="AC_INV_APPRR" type="String" length="15"
not-null="false" />
<property name="Number" column="AC_INV_NUMBR" type="String" length="15"
not-null="true" />
<property name="Produced" column="AC_INV_PROD" type="DateTime"
not-null="false" />
<many-to-one class="Customer" column="AC_CUST_ID" />
</class>
</hibernate-mapping>
Invoice
public class Invoice {
public Invoice() {
Items = new List<Item>();
Produced = DateTime.Now;
}
public virtual DateTime? Approved { get; protected set; }
public virtual string Approver { get; protected set; }
public virtual Customer Customer { get; set; }
public virtual int Id { get; proected set; }
public virtual string Number { get; set; }
public virtual DateTime? Produced { get; set; }
public virtual void Approve(string approver) {
Approved = DateTime.Now;
Approver = approver;
}
public virtual void Reject() { Produced = null; }
}
CustomerRepository
public class CustomerRepository {
public CustomerRepository(ISession session) { Session = session; }
public ISession Session { get; protected set; }
public Customer Save(Customer instance) {
Session.SaveOrUpdate(instance);
return Instance;
}
}
Related articles
nHibernate one-to-many inserts but doesnt update
NHibernate - code example for update
Any help appreciated.
Don't forget to Flush() your session!
NHibernate keeps track of all the changes along a session lifecycle which should only live for a form (Desktop) or a page (Web).
Because of the session is aware of all the changes, it doesn't necessarily commit the changes to the underlying database. Instead, it keeps a record of all the changes into a dictionary, then when it is flushed by calling ISession.Flush(), you actually demand the session to commit the changes for good.
So solution shall be:
repository.Save(customer);
session.Flush();
Or you may as well code a Commit() method within your repository which would Flush() the session upon a call.
repository.Save(customer);
repository.Commit();
And your repository would look like:
// Assuming you stock your session in the `Session` property.
public void Commit() { Session.Flush(); }
That's all!
I have a rather odd requirement in my fluent hibernate maps. I have an table(A) which has a compound foreign key relationship with another table(B). In the mapping for table A I would like to have both the object created from table B and access to the individual attributes of A which define the key. Is there any way to do that? I seem to get index out of range exceptions if I map the column twice.
I cannot just explore B for the attributes because the row in table B may not exist. I am painfully aware that there are some significant smells in the structure with which I'm dealing. Such is the fate of those who deal with legacy systems.
It's kinda possible, by hacking around a little.
We're going to define a domain that with a fake collection that we'll use to retrieve the single related element, if found:
public class Foo
{
public virtual BarKey BarKey { get; set; }
public virtual Bar Bar { get { return Bars.SingleOrDefault(); } }
protected virtual ICollection<Bar> Bars { get; set; }
}
public class Bar
{
public virtual BarKey Id { get; set; }
}
//this class must override Equals and GetHashcode. Implementation not shown.
public class BarKey
{
public virtual int X { get; set; }
public virtual int Y { get; set; }
}
The BarKey component contains the properties that are part of the key.
Now, the mapping:
<class name="Foo">
<id ...><generator .../></id>
<component name="BarKey">
<property name="X" />
<property name="Y" />
</component>
<bag name="Bars" inverse="true">
<key property-ref="BarKey">
<column name="X"/>
<column name="Y"/>
</key>
<one-to-many class="Bar"/>
</bag>
</class>
<class name="Bar">
<composite-id name="Id">
<key-property name="X" />
<key-property name="Y" />
</composite-id>
</class>
The property-ref attribute there tells NH to match those columns in Bar against the BarKey property of Foo instead of its Id.
I'm starting to learn NHibernate (3.0) and picked up a copy of Packt Publishing's NHibernate 3.0 Cookbook.
There's a section on one-to-many mappings which I'm walking through but with my own database. It suggests I should do something like this to model a one to many relationship between customers and their domains:
public class Customer
{
public virtual int Id { get; protected set; }
public virtual string CustomerName { get; set; }
// Customer has many domains
public virtual IList<Domain> Domains { get; set; }
}
public class Domain
{
public virtual int Id { get; protected set; }
public virtual int CustomerID { get; set; }
public virtual string DomainName { get; set; }
}
Customer Mapping:
<class name="Customer" table="tblCustomer">
<id name="Id">
<column name="CustomerID" sql-type="int" not-null="true"/>
<generator class="identity"/>
</id>
<property name="CustomerName" column="Customer"/>
<list name="Domains">
<key column="CustomerID"/>
<one-to-many class="Domain" />
</list>
</class>
When I run this I get the following error:
XML validation error: The element 'list' in namespace 'urn:nhibernate-mapping-2.2' has invalid child element 'one-to-many' in namespace 'urn:nhibernate-mapping-2.2'. List of possible elements expected: 'index, list-index' in namespace 'urn:nhibernate-mapping-2.2'.
The book's example is a bit more complex in that they use table-per-subclass mappings:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Eg.Core"
namespace="Eg.Core">
<subclass name="Movie" extends="Product">
<property name="Director" />
<list name="Actors" cascade="all-delete-orphan">
<key column="MovieId" />
<index column="ActorIndex" />
<one-to-many class="ActorRole"/> <-- Is this wrong?
</list>
</subclass>
</hibernate-mapping>
I'm guessing the book is wrong?
No, your mapping is missing the index element. A list in NHibernate is an ordered set, if you want an unordered set use bag mapping.
I have the following entity:
public class Alert
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IDictionary<CxChannel, string> Messages { get; set; }
}
public enum CxChannel
{
Message,
Email
}
and following mapping:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Entities.Alert, Entities" table="alert">
<id name="Id" type="int" unsaved-value="0" access="property">
<generator class="identity"/>
</id>
<property name="Name" column="name"/>
<map name="Messages" table="alert_message" cascade="all">
<key column="alert_id"/>
<index column="channel" type="Entities.CxChannel, Entities"/>
<element column="message" type="System.String"/>
</map>
</class>
</hibernate-mapping>
The problem is that when I save an alert entity, Messages dictionary is not persisted to database. As a matter of fact my code looks like the code Oren used in his blog post: http://ayende.com/Blog/archive/2009/06/03/nhibernate-mapping-ndash-ltmapgt.aspx
Has anyone experienced same issue?
Verify that your channel column is an integer in your schema as the CxChannel enum will be mapped as 0 (for Message) and 1 (for email). I just pasted your code and mappings into a console project, used new SchemaExport(cfg).Execute(false, true, false), and successfully inserted rows into the generated database.
I have a class:
public class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IDictionary<string, string> Attributes { get; set; }
}
and a mapping file:
<class name="User" table="Users">
<id name="Id">
<generator class="hilo"/>
</id>
<property name="Name"/>
<map name="Attributes" table="UserAttributes">
<key column="UserId"/>
<index column="AttributeName" type="System.String"/>
<element column="Attributevalue" type="System.String"/>
</map>
</class>
So now I can add many attributes and values to a User.
How can I query those attributes so I can get ie.
Get all the users where attributename is "Age" and attribute value is "20" ?
I don't want to do this in foreach because I may have millions of users each having its unique attributes.
Please help
You can do it using HQL.
For example:
from User u join u.Attributes attr
where index(attr) = 'Age' and attr = '20'