<hibernate-mapping package="com.test.info">
<class name="LevelInfo">
<composite-id>
<key-property column="ID" name="id"/>
</composite-id>
<property column="NAME" name="name"/>
<property column="DESC" name="desc"/>
<bag name="details" cascade="all" order-by="ID" >
<key column="ID"/>
<one-to-many class="DeatilInfo"/>
<loader query-ref="includedDetailsSql"/>
</bag>
</class>
<sql-query name="includedDetailsSql" >
<load-collection alias="details" role="LevelInfo.details"/>
SELECT {details.*} FROM
DETAILS_TEMP detail
WHERE detail.ID = ?
AND detail.CODE=:myParam
</sql-query>
</hibernate-mapping>
The problem is that I want to pass parameter i.e myParam for filtering the records in the collection loading sql. Hibernate has passed the foreign key ID in the above query but I could not find a way to pass any other parameter in this query dynamically.
Further I also want to sort collection elements on a field that can be set dynamically. So is there any way to set order by attribute value in bag mapping dynamically.
Related
A customer can have several contact. The code below is working but... the contacts for this customer are deleted first and after inserted again. Is not possible to avoid this delete and just insert the new one ?
<class name="Customer" table="Customer">
<id name="Id">
<generator class="native" />
</id>
<property name="LastName" not-null="true" />
<bag name="Contacts" cascade="all-delete-orphan" table="CustomerContact">
<key column="Customer" not-null="true" />
<many-to-many class="Contact"/>
</bag>l
</class>
<class name="Contact" table="Contact">
<id name="Id">
<generator class="native" />
</id>
<property name="LastName" not-null="true" />
<many-to-one name="Customer" column="Customer" not-null="true" />
</class>
public virtual void AddContact(Contact contact)
{
Contacts.Add(contact);
contact.Customer = this;
}
When I do this code twice, to add 2 contacts :
Contact contact = new Contact() { LastName = "MyLastName" };
Customer customer = session.Get(customerId);
customer.AddContact(contact);
session.Update(customer);
You are using a bag for your collection, a bag can contain duplicates, does not maintain order and there is no way to identify an individual entry in the bag distinctly with SQL.
That is why NH removes and inserts all assigned entities. Use a set if it suits your requirements instead.
This typically happens when NH lost the persistence information about the collection. It assumes that you changed the whole collection. To update the database efficiently, it removes all items in one query (delete ... where customer = 5) and inserts the new items.
You probably don't return the collection provided from NH.
Typical mistake:
IList<Contact> Contacts
{
get { return contacts; }
// wrong: creates a new List and replaces the NH persistent collection
set { contacts = value.ToList(); }
}
By the way, you should make the collection inverse, since it is a redundancy to the contact.Customer relation:
<bag name="Contacts" cascade="all-delete-orphan" table="CustomerContact" inverse="true">
<key column="Customer" not-null="true" />
<many-to-many class="Contact"/>
</bag>
I've been fighting with an NHibernate set-up for a few days now and just can't figure out the correct way to set out my mapping so it works like I'd expect it to.
There's a bit of code to go through before I get to the problems, so apologies in advance for the extra reading.
The setup is pretty simple at the moment, with just these tables:
Category
CategoryId
Name
Item
ItemId
Name
ItemCategory
ItemId
CategoryId
An item can be in many categories and each category can have many items (simple many-to-many relationship).
I have my mapping set out as:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="..."
namespace="...">
<class name="Category" lazy="true">
<id name="CategoryId" unsaved-value="0">
<generator class="native" />
</id>
<property name="Name" />
<bag name="Items" table="ItemCategory" cascade="save-update" inverse="true" generic="true">
<key column="CategoryId"></key>
<many-to-many class="Item" column="ItemId"></many-to-many>
</bag>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="..."
namespace="...">
<class name="Item" table="Item" lazy="true">
<id name="ItemId" unsaved-value="0">
<generator class="native" />
</id>
<property name="Name" />
<bag name="Categories" table="ItemCategory" cascade="save-update" generic="true">
<key column="ItemId"></key>
<many-to-many class="Category" column="CategoryId"></many-to-many>
</bag>
</class>
</hibernate-mapping>
My methods for adding items to the Item list in Category and Category list in Item set both sides of the relationship.
In Item:
public virtual IList<Category> Categories { get; protected set; }
public virtual void AddToCategory(Category category)
{
if (Categories == null)
Categories = new List<Category>();
if (!Categories.Contains(category))
{
Categories.Add(category);
category.AddItem(this);
}
}
In Category:
public virtual IList<Item> Items { get; protected set; }
public virtual void AddItem(Item item)
{
if (Items == null)
Items = new List<Item>();
if (!Items.Contains(item))
{
Items.Add(item);
item.AddToCategory(this);
}
}
Now that's out of the way, the issues I'm having are:
If I remove the 'inverse="true"' from the Category.Items mapping, I get duplicate entries in the lookup ItemCategory table.
When using 'inverse="true"', I get an error when I try to delete a category as NHibernate doesn't delete the matching record from the lookup table, so fails due to the foreign key constraint.
If I set cascade="all" on the bags, I can delete without error but deleting a Category also deletes all Items in that category.
Is there some fundamental problem with the way I have my mapping set up to allow the many-to-many mapping to work as you would expect?
By 'the way you would expect', I mean that deletes won't delete anything more than the item being deleted and the corresponding lookup values (leaving the item on the other end of the relationship unaffected) and updates to either collection will update the lookup table with correct and non-duplicate values.
Any suggestions would be highly appreciated.
What you need to do in order to have your mappings work as you would expect them to, is to move the inverse="true" from the Category.Items collection to the Item.Categories collection. By doing that you will make NHibernate understand which one is the owning side of the association and that would be the "Category" side.
If you do that, by deleting a Category object it would delete the matching record from the lookup table as you want it to as it is allowed to do so because it is the owning side of the association.
In order to NOT delete the Items that are assigned to a Category object that is to be deleted you need to leave have the cascade attribe as: cascade="save-update".
cascade="all" will delete the items that are associated with the deleted Category object.
A side effect though would be that deleting the entity on the side where the inverse=tru exists will thow a foreign key violation exception as the entry in the association table is not cleared.
A solution that will have your mappings work exactly as you want them to work (by the description you provided in your question) would be to explicitly map the association table.
Your mappings should look like that:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="..."
namespace="...">
<class name="Category" lazy="true">
<id name="CategoryId" unsaved-value="0">
<generator class="native" />
</id>
<property name="Name" />
<bag name="ItemCategories" generic="true" inverse="true" lazy="true" cascade="none">
<key column="CategoryId"/>
<one-to-many class="ItemCategory"/>
</bag>
<bag name="Items" table="ItemCategory" cascade="save-update" generic="true">
<key column="CategoryId"></key>
<many-to-many class="Item" column="ItemId"></many-to-many>
</bag>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="..."
namespace="...">
<class name="Item" table="Item" lazy="true">
<id name="ItemId" unsaved-value="0">
<generator class="native" />
</id>
<property name="Name" />
<bag name="ItemCategories" generic="true" inverse="true" lazy="true" cascade="all-delete-orphan">
<key column="ItemId"/>
<one-to-many class="ItemCategory"/>
</bag>
<bag name="Categories" table="ItemCategory" inverse="true" cascade="save-update" generic="true">
<key column="ItemId"></key>
<many-to-many class="Category" column="CategoryId"></many-to-many>
</bag>
</class>
</hibernate-mapping>
As it is above it allows you the following:
Delete a Category and only delete the entry in the association table without deleting any of the Items
Delete an Item and only delete the entry in the association table without deleting any of the Categories
Save with Cascades from only the Category side by populating the Category.Items collection and saving the Category.
Since the inverse=true is necessary in the Item.Categories there isn't a way to do cascading save from this side. By populating the Item.Categories collection and then saving the Item objec you will get an insert to the Item table and an insert to the Category table but no insert to the association table. I guess this is how NHibernate works and I haven't yet found a way around it.
All the above are tested with unit tests.
You will need to create the ItemCategory class mapping file and class for the above to work.
Are you keeping the collections in synch? Hibernate expects you, I believe, to have a correct object graph; if you delete an entry from Item.Categories, I think you have to delete the same entry from Category.Items so that the two collections are in sync.
I have the following mapping, the many-to-one property 'Message' has a corresponding one-to-many association in the 'RootMessage' class.
<class name="IMessageReceipt" lazy="false" table="MessageReceipts" abstract="true">
<id name="Id">
<generator class="guid.comb"></generator>
</id>
<discriminator column="Discriminator"/>
<property name="Address" />
<property name="Status" />
<property name="MarkedAsDeleted" />
<many-to-one name="Message" column="MessageId" class="RootMessage"
not-found="ignore"/>
<subclass name="MessageReceipt" lazy="false" discriminator-value="1">
</subclass>
</class>
The many-to-one association refuses to load when using the criteria api (all I get is NULL), here is an example of a query:
List<IMessageReceipt> list;
using (var tx = Session.BeginTransaction())
{
var criteria = Session.CreateCriteria(typeof (IMessageReceipt));
criteria.Add(Restrictions.Eq("Address", address));
criteria.Add(Restrictions.Eq("Status", status));
criteria.SetFirstResult(0);
criteria.SetMaxResults(quantity);
list = criteria.List<IMessageReceipt>().ToList();
tx.Commit();
}
return list;
Any ideas?
Ok so after almost a day of chagrin I have the solution. NHibernate doesn't automatically assume a bi-directional association between two entities even if you have mappings between both. You need to imperatively declare the associations in your code before persisting. Thus:
message.Receipts = receipts;
foreach (var receipt in receipts)
{
receipt.Message = message;
}
Session.Save(message);
tx.Commit();
Also inverse="true" should be applied to the side with the collection member:
<set name="Receipts" inverse="true" cascade="save-update">
<key column="MessageId"></key>
<one-to-many class="IMessageReceipt"/>
</set>
Consider these two classes mapped to the same table. One is readonly via mutable="false".
<class name="Funder" table="funder">
<id name="id">
<generator class="identity" />
</id>
<property name="funder_name" />
<property name="contact_name" />
<property name="addr_line_1" />
<property name="addr_line_2" />
<property name="addr_line_3" />
<property name="city" />
<many-to-one name="state" column="state_id" foreign-key="FK_funder_state_id" fetch="join" />
<property name="zip_code" length="10" />
<property name="phone_number" length="30" />
<property name="create_dt" update="false" not-null="true" />
<many-to-one name="create_by" column="create_by" not-null="true" update="false" foreign-key="FK_funder_create_by" fetch="join" />
<property name="last_update_dt" insert="false" />
<many-to-one name="last_update_by" insert="false" foreign-key="FK_funder_last_update_by" fetch="join" />
</class>
<class name="FunderSimple" table="funder" schema-action="none" mutable="false">
<id name="id">
<generator class="identity" />
</id>
<property name="funder_name" />
<property name="contact_name" />
<property name="phone_number" />
</class>
If I move the FunderSimple mapping before the Funder mapping my schema does not generate correctly. If I leave it as is above, it works.
Is this by design? It seems as though the schema-action="none" sticks to the table_name and later mappings to the same table will not generate the schema.
I'm doing it like this because I have another class named Contract which has a foreign key to the funder table. However, I don't need all the funder columns when referencing from the contract object.
<many-to-one name="funder_simple" column="funder_id" foreign-key="FK_contract_funder_id" fetch="join" />
Funder does not inherit from FunderSimple.
Should I be using a different technique to fetch only a subset of columns from a foreign key table? Is many-to-one the only way to setup a foreign key?
using version 2.1.0.4000
For such situations, I use projections instead.
I've never mapped two types to the same table (unless for inheritance reasons).
So, what I do in such a situation is:
create the FunderSimple class, and import it so that it is known by NHibernate:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<import class="MyNamespace.FunderSimple" />
</hibernate-mapping>
Once you've done this, you can create a query on your 'Funder' type, with the ICriteria API, but, you could specify that you would like NHibernate to return instances of FunderSimple.
By doing so, NHibernate is smart enough to generate a simplified SQL query, that only retrieves the columns that are necessary to populate instances of the FunderSimple class.
This is done like this:
ICriteria crit = session.CreateCriteria (typeof(Funder));
// add some expressions ...
crit.Add ( ... );
// Now, set the projection, and specify that FunderSimple should be returned
crit.SetProjection (Projections.ProjectionList()
.Add (Projections.Property ("Id"), "Id")
.Add (Projections.Property ("funder_name"), "funder_name")
.Add (Projections.Property ("phone_number"), "phone_number"));
crit.SetResultTransformer (Transformers.AliasToBean (typeof(FunderSimple)));
crit.List <FunderSimple>();
I started using NHibernate this week (struggling). I have a small application with 3 tables (I use 2 for now). Table currency and table country here are the mapping files.
<class name="dataprovider.Country,dataprovider" table="country">
<id name="CountryId" column="country_id" unsaved-value="0">
<generator class="native"/>
</id>
<!--<bag name="BatchList" inverse="true" lazy="true" >
<key column="country_id" />
<one-to-many class="Batch" />
</bag>
<bag name="PrinterList" inverse="true" lazy="true" >
<key column="country_id" />
<one-to-many class="Printer" />
</bag>-->
<many-to-one name="CurrencyId" column="currency_id" class="Currency" cascade="save-update"/>
<!--<property column="currency_id" name="Currency_Id"/>-->
<property column="name" name="Name"/>
<property column="region" name="Region" />
<property column="comments" name="Comments"/>
</class>
The currency mapping file:
<class name="dataprovider.Currency, dataprovider" table="currency">
<id name="CurrencyId" column="currency_id" >
<generator class="native"/>
</id>
<bag name="CountryList" inverse="true" lazy="true" >
<key column="currency_id" />
<one-to-many class="Country" />
</bag>
<!--<bag name="DenominationList" inverse="true" lazy="true" >
<key column="currency_id" />
<one-to-many class="Denomination" />
</bag>-->
<property column="name" name="Name"/>
<property column="authorizer" name="Authorizer" />
<property column="date_created" name="DateCreated" type="DateTime" not-null="true" />
<property column="comments" name="Comments" />
The many to one relationship that country hold create an attribute of the type Currency in the country persistence class. Now while my test can_add_currency and can_add_country succeeded (I export the schema) I have null value in the table country on the field currency_id.
Here is the test code:
[Test]
public void can_add_new_country()
{
CountryManager cm = new CountryManager();
Country country = new Country();
//country.CurrencyId = CurrencyManager.GetCurrencyById(1);
country.CurrencyId = new CurrencyManager().GetCurrencyById(1);
country.Name = "POUND";
country.Region = "ENGLAND";
country.Comments = "the comment";
cm.Add(country);
using(ISession session = _sessionFactory.OpenSession())
{
Country fromdb = session.Get<Country>(country.CountryId);
Assert.IsNotNull(fromdb);
Assert.AreNotSame(fromdb, country);
}
}
public Currency GetCurrencyById(int currency_id)
{//GetCurrencyById from CurrencyManger class
try
{
using(ISession session = NHibernateHelper.OpenSession())
{
return session.Get<Currency>(currency_id);
}
} catch (Exception ex)
{
return null;
}
}
The question is: how to insert into table country with the currency_id of an existing currency_id from the table currency?
How do you guys/gals do it? I'm seriously stuck and a 2 day small project is taking me one week now.
Set cascade="save-update" on your bag name="CountryList". If that doesn't work, it may be helpful to post your code for CountryManager.Add() to see how the saving is taking place.
In response to your second question, if I understand it correctly, this is how NHibernate treats mapped collections:
You mapped the collection as lazy, so loading the object will not load all the elements of the collection at the same time. Instead, when you first access the collection NHibernate will query the database to fill the collection and return it. So, when you first do something like:
var countries = currency.CountryList;
or
foreach (Country country in currency.CountryList)
NHibernate will silently execute a query similar to:
SELECT * FROM country WHERE currency_id = ?
And then build a collection of Country objects to return (and cache so that the query is not run again).
Basically, through the mapping file you've already told NHibernate all about your two entities (Country and Currency) and how they are related so it knows how to build the queries to access the data. Similarly, it keeps track of what was in the collection so when you add or remove items, it can compare what was changed and execute the appropriate INSERT or REMOVE statements.
So, the way to use collections mapped by NHibernate is to use them just as you would with a normal .NET collection. Add and remove items at your will. Just when you are finished, make sure you tell NHibernate to persist the changes you made to the database, either by calling session.Save() or session.Delete() on every item you add/remove or (if you have set cascading, like you have) simple call session.Save() on the parent object that contains the collection.
My cascade was rather on the this side:
<many-to-one name="CurrencyId" column="currency_id" class="Currency" cascade="save-update"/>
After I changed it it wasn't working at first even though I rebuild the solution. Then I did another test from the Currency_Test class: can_get_currency_by_id which call the same function GetCurrncyById and I could have an object while from can_add_new_country the same function returns null.
Then I realise that the ExportSchema of either Country_Test [Setup] or Currency_Test is not creating the database on time for the can_add_new_product to have a currency object. That's why it's returning null object.
Now not to abuse; but can you tell me how to use IList<Counrty>CountryList? I don't know if I put it well. From my understanding it should store all country objects using the same currency (currency_id reference). How does NHibernate do that?