NHibernate does not update Index-column when removing an object from a list - nhibernate

My class A has a property:
public virtual IList<IVisitor> Visitors { get; set; }
which is mapped as follows:
<list name="Visitors" table="Visitor" cascade="all-delete-orphan">
<key column="SomeID" not-null="true" update="false" />
<index column="idx" />
<one-to-many class="Visitor" />
</list>
When there are 3 objects in the list, they have the idx 0, 1, and 2.
Now when I remove the middle object and save my instance of A like:
a.Visitors.RemoveAt( 1 );
a.Save(); // calls session.SaveOrUpdate( a );
the appropriate row is deleted but the other two rows keep its idx 0 and 2.
Why is NHibernate not updating it to 0 and 1?
Thank you.

Indexes are not updated because your mapping mandates them not to be updated.
Remove update="false" from your key mapping. It does apply to index too. The index is considered part of the list key.
If you use an old version of NHibernate, you may additionally have this old bug.

Related

NHibernate Mapping - many-to-one via table

I'm working with an existing database that has the following structure. Changing the database schema is a last resort.
Products
Id
Name
ParentProducts
ParentId
ChildId
I don't want an entity for ParentProducts, I have the following for the children property (still need to test it, but that's the concept).
<bag name="Children" lazy="true" table="dbo.ParentProducts" cascade="save-update" inverse="true" >
<key column="[ChildId]"></key>
<many-to-many column="[ProductId]" class="Product" />
</bag>
What I'm struggling with is how do I create a Parent property? I'd like to do something like the following, but table isn't a valid attribute for many-to-one.
<many-to-one name="Parent" column="[ParentId]" table="dbo.ParentRelated" class="Policy" />
I could create a bag and only ever look at the first item, but that's more of a hack.
Any ideas?
Thanks
Creating a bag is the easiest solution. And you can give it a clean interface:
protected virtual ICollection<Product> Parents { get; set; }
public virtual Product Parent
{
get
{
return Parents.SingleOrDefault();
}
set
{
Parents.Clear();
Parents.Add(value);
}
}
With this, the rest of the code doesn't need to be aware of the DB/mapping structure.

NHibernate - Mapping tagging of entities

I've been wrecking my mind on how to get my tagging of entities to
work. I'll get right into some database structuring:
tblTag
TagId - int32 - PK
Name
tblTagEntity
TagId - PK
EntityId - PK
EntityType - string - PK
tblImage
ImageId - int32 - PK
tblBlog
BlogId - int32 - PK
class Image
Id
EntityType { get { return "MyNamespace.Entities.Image"; }
IList<Tag> Tags;
class Blog
Id
EntityType { get { return "MyNamespace.Entities.Blog"; }
IList<Tag> Tags;
The obvious problem I have here is that EntityType is an identifer but
doesn't exist in the database. If anyone could help with the this
mapping I'd be very grateful.
You don't need the entity type. Take a look at any-type mapping (it stores the type name in the database in the relation table, but you don't need it in the entity model).
See this blog post by ayende.
Edit: tried to write an example.
You could have an own table for each tagged object, this is easy and straight forward, you don't even need any types:
<class name="Tag">
<!-- ... -->
<property name="Name"/>
</class>
<class name="Image">
<!-- ... -->
<bag name="Tags" table="Image_Tags">
<key column="Image_FK"/>
<many-to-many class="Tag" column="TagId "/>
</bag>
</class>
Tried to use some advanced features to map it into a single table, but I think it doesn't work this way:
<class name="Tag">
<!-- ... -->
<property name="Name"/>
<bag name="Objects" table="tblTagEntity" access="noop">
<key column="TagId"/>
<many-to-any id-type="System.Int64" meta-type="System.String">
<meta-value
value="IMAGE"
class="Image"/>
<meta-value
value="BLOG"
class="Blog"/>
<column name="EntityType"/>
<column name="EntityId"/>
</many-to-any>
</bag>
</class>
<class name="Image">
<!-- ... -->
<bag name="Tags" table="tblTagEntity" where="EntityType='IMAGE'">
<key column="EntityId"/>
<many-to-many class="Tag" column="TagId "/>
</bag>
</class>
The tricks here are:
access="noop" to specify the foreign key without having a property in the entity model, see this post.
where="EntityType='IMAGE'" to filter the loaded data.
The problem is that most probably the EntityType is not set to any useful value. This could be fixed somewhere, but I don't think that it is worth the effort.
Someone else has probably a better idea.
Edit 2: another (working) solution
make the association table an entity:
in short:
Tag => TagEntity: not mapped or one-to-many inverse (noop)
TagEntity => Tag: many-to-one
TagEntity => Object: any
Object => TagEntity: one-to-many inverse
This should work straight forward.
classes:
class Tag
{
string Name { get; set; }
}
class TagEntity
{
Tag Tag { get; set; }
object Entity { get; set; }
}
class Image
{
IList<TagEntity> tags { get; private set; }
}
The only drawback seems to be that you have to make sure that the bidirectional associations are consistent without loading to much data. Note that inverse collections are not stored.
Edit 2: Performance notes
When you add / remove tags, you could do a trick. TagEntity has a reference to the tagged entity. The Entity also has a list of TagEntities, but this is marked as inverse. (This means, they are loaded, but not stored.)
You can add and remove tags without loading the Entity an without loading all the tags.
Adding:
Get Tag to add (or load proxy if you have the id of the tag)
Load Entity (just proxy, using session.Load, no db access here)
create new TagEntity, assign tag and entity-proxy
save TagEntity
Removing:
Get TagEntity to remove
delete TagEntity.
Within the session, you don't have this tag assigned to/removed from the TagEntity. This works fine assumed that you only add or remove tags within this transaction.
I you define a list of TagEntities on the Tag, you can do the same, without loading all the TagEntities just to add or remove one.
You could make EntityType an Enum in your code. And/or, you could try making EntityType an actual entity in your database (tblEntityType).
Got Stefans final solution to work! Here's my final mappings:
Image
<bag name="TagEntites" table="tblTagEntity" cascade="all" fetch="join" inverse="true" where="EntityType='EntityImage'">
<key column="EntityId"></key>
<one-to-many class="TagEntity" />
</bag>
TagEntity
<id name="Id">
<column name="TagEntityId"></column>
<generator class="identity" />
</id>
<any name="Entity" id-type="System.Int32" meta-type="System.String">
<meta-value value="EntityImage" class="Image" />
<column name="EntityType"></column>
<column name="EntityId"></column>
</any>
<many-to-one name="Tag" class="Tag" cascade="all" fetch="join">
<column name="TagId"></column>
</many-to-one>

How do I map List<List<EntityClass>> in NHibernate?

I have a Rotation class, which contains references to multiple lists of Advert objects. I would prefer an implementation where Rotation has a property of type List<List<Advert>> to hold these, but I am unable to come up with an NHibernate mapping supporting this.
In the database schema, the many-to-many relation between Rotation and Advert is represented as a table RotationAdvert with the following columns:
RotationID
AdvertID
Variant ("horizontal position" / index within outer list)
Position ("vertical position" / index within inner list)
The best solution I have found yet, is to implement a fixed number of List<Advert> typed properties on Rotation and extend the mapping with a <list> element for each:
<list name="Variant1" table="RotationAdvert" where="Variant = 1">
<key column="RotationID"/>
<index column="Position"/>
<many-to-many class="Advert" column="AdvertID"/>
</list>
<list name="Variant2" table="RotationAdvert" where="Variant = 2">
<key column="RotationID"/>
<index column="Position"/>
<many-to-many class="Advert" column="AdvertID"/>
</list>
etc...
However, this requires me to specify a fixed number of variants, which I really would like to avoid.
What are my other options? Can I squeeze a RotationVariant class into the model - without creating new tables in the database - and somehow map a List<RotationVariant> property on Rotation? Or will I have to create a new table in the database, just to hold an ID for each RotationVariant?
I just ran into this same problem today. Reading through the Hibernate documentation and found an answer in section 6.1: Collection Mapping https://www.hibernate.org/hib_docs/nhibernate/html/collections.html:
Collections may not contain other collections
I couldn't find this exact sentence in the NHibernate docs, but it seems that the same rule applies. Like Stephan and Chris said, you will probably need to have another entity to hold the values.
The best I can think of is to adapt the model to the desired database structure.
class Rotation
{
IList<AdvertVariant> AdvertVariants { get; private set; }
}
class AdvertVariant
{
int Variant { get; set; }
Advert Advert { get; set; }
}
mapping:
<class name="Rotation">
<list name="AdvertVariants" table="RotationAdvert" >
<key column="RotationID"/>
<index column="Position"/>
<one-to-many class="AdvertVariant"/>
</list>
</class>
<class name="AdvertVariant">
<property name="Variant" />
<many-to-one name="Advert" column="VariantId"/>
</class>
Database:
Rotation:
id
AdvertVariant
id
Variant
RotationId
VariantId
Advert
id
Then you can easily create properties like this:
class Rotation
{
//...
IDictionary<int, IList<Adverts>> AdvertsByVariant
{
return VariantAdverts.ToDictionary(x => x.Variant, y => y.Advert);
}
}
I'd propose to have a Rotation class which holds a list of Adverts. An advert then holds a list of child adverts in a parent child relationship.

Delete a child record from the parent collection

I'm developing a sample application so that I can learn the ins and outs of NHibernate. I am struggling with a delete issue. I wish to be able to delete a child record by removing it from its parent’s collection and then saving the parent. I have setup a bidirectional one-to-many relationship and inserting/updating is working great.
Here are my mappings
Basket:
<bag name="Items" inverse="true" cascade="all">
<key column="BasketId" />
<one-to-many class="BasketItem" />
</bag>
BasketItem:
<many-to-one not-null="true" name="Basket" column="BasketId" />
I would like to call basket.RemoveBasketItem(BasketItem item) then Session.SaveUpdate(basket) so that the basket item will be deleted. Is this possible?
Change cascade="all" into cascade="all-delete-orphan".
cascade="all" will only delete your child records if the parent gets deleted.
I have same scenario and ive used cascade="all-delete-orphan" in bagList but when i delete a single child item in a collection it deletes the parent object as well.
I was having the same problem as initforthemoney due to returning a new list as ReadOnly from my collection getter. I found I could continue to use the ReadOnly list by changing the property access strategy of the collection from nosetter to field.
I was having a problem where my children elements where returning an Ordered enumerable.
private readonly IList<Child> children;
public virtual IEnumerable<Child> Children { get { return children.OrderBy(c => c.Position); } }
public virtual void DeleteChild(Child item)
{
children.Remove(item);
}
I moved the ordering to my mapping and returned the children as it is for the IEnumerable. This worked for me!

Insert into child table with NHibernate 2.0.1

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?