I have two classes:
public class Parent
{
public virtual long? ID { get; set; } // native
public virtual IList<Child> Children { get; set; }
public virtual string Name { get; set; }
}
public class Child
{
public virtual long ID { get; set; } // assigned
public virtual string Name { get; set; }
}
Instantiating and saving parent and child:
child = new Child() { ID = 1, Name = "SomeName" };
parent = new Parent() { Children = new List() { child } };
session.Save(parent);
Which gives me:
NHibernate.StaleStateException: Unexpected row count: 0; expected: 1.
I think the problem is with the assigned id on the child. Since it has an id, NHibernate thinks it has previously saved before which is not the case.
The generated (trimmed & renamed) SQL is:
NHibernate: select child0_.ID as child1_1_, child0_.NAME as NAME1_, child0_.PARENT_ID as COMMAND7_1_, from CHILD child0_
NHibernate: select parent0_.PARENT_ID as parent1_10_
NHibernate: select parent0_.PARENT_ID as parent1_10_, parent0_.NAME as parent2_10_ from PARENT parent0_
NHibernate: UPDATE CHILD SET PARENT_ID = #p0 WHERE CHILD_ID = #p1;#p0 = 2, #p1 = 1
Mapping files:
<class name="MyNamespace.Child" table="CHILD">
<id name="ID" column="CHILD_ID" type="System.Int64">
<generator class="assigned"></generator>
</id>
<property name="Name" column="NAME"></property>
</class>
<class name="MyNamespace.Parent" table="PARENT">
<id name="ID" column="PARENT_ID" type="System.Int64">
<generator class="native"></generator>
</id>
<property name="Name" column="NAME"></property>
<bag name="Children">
<key column="PARENT_ID"></key>
<one-to-many class="MyNamespace.Child"></one-to-many>
</bag>
</class>
While searching google, I found about version tag which may be a solution but I do not have a persistent field to use as version. In this case, how can I save (insert) a child with assigned id and its parent?
When cascading from a parent to a child, NHibernate uses the SaveOrUpdate method. You are correct that NHibernate need some way to determine whether it should perform an insert or an update. It will look at three different fields for an unsaved value to determine if the entity is new.
Id
Version
Timestamp
With an assigned Id, you will need either a Version or Timestamp field in order to indicate that the entity is new.
An alternative would be to call Save() on the children explicitly.
I'm not 100% sure if this is the same problem you are having, but my database is 100% assigned id (ugh) and I had to create an Interceptor that kept track of whether a child is or isn't persisted for cascades to work.
The code is cut/paste (which is why it has dumb names... I didn't understand 100% at first!), and I initially got 90% of it from online documentation (which I can't find via google right now ... sorry):
Base class you put on object that has an assigned ID you want to cascade:
public class Persistent
{
private bool _saved = false;
public virtual void OnSave()
{
_saved = true;
}
public virtual void OnLoad()
{
_saved = true;
}
public virtual bool IsSaved
{
get { return _saved; }
}
}
The interceptor you add to session:
public class TrackingNumberInterceptor : EmptyInterceptor
{
public override bool? IsTransient(object entity)
{
if (entity is Persistent)
{
return !((Persistent)entity).IsSaved;
}
else
{
return null;
}
}
public override bool OnLoad(object entity, object id, object[] state, string[] propertyNames, IType[] types)
{
if (entity is Persistent) ((Persistent)entity).OnLoad();
return false;
}
public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, IType[] types)
{
if (entity is Persistent) ((Persistent)entity).OnSave();
return false;
}
}
Basically the idea is that since NHibernate doesn't know if an assigned id entity is persisted or not, you keep track for it.
By default the object starts with persisted (_saved) at false. When the entity is either loaded or saved by NHibernate, the trigger sets the objects persisted (_saved) flag to true.
So for a fresh item that isn't persisted, it starts at false and stays false because NHibernate has never saved or loaded it. When NHibernate checks whether the child is transient, the trigger responds that it is transient, and a save happens which marks the child as persisted. Also now any future use will require a load which again marks it as persisted.
Calling session.SaveOrUpdate( childObject ) should solve the problem.
Related
I have a model like this:
public class Order
{
public virtual int OrderType { get; set; }
}
(lots of other properties omitted of course) which maps directly to an int type in the DB.
The thing is, the numeric order type is meaningless to my application. There are single-letter codes that the user sees which denote the order type. So, I could do something like this:
public class Order
{
public virtual int OrderTypeIgnored { get; set; }
public virtual char OrderType
{
get
{
return translateForward(OrderTypeIgnored);
}
set(char val)
{
OrderTypeIgnored = translateBackward(val);
}
}
}
(lots of air code/pseudocode there, I'm relatively new to C#) and just map the OrderTypeIgnored property. But is there a cleaner way to do this? Perhaps somehow overriding the getter and setter on the mapped property itself?
A few notes: The values are static enough that embedding the translation in the code is not a problem. No, there's no LOV table, and no, I don't have control over the database structure.
Sorry if there are answers for this, but searching for things like "mapping" and "translation" don't really get me the results I'm looking for, obviously.
You could create a public char property that uses a private int field and only map the field.
Model:
public class Order
{
private int _orderType;
public virtual char OrderType
{
get
{
return TranslateForward(_orderType);
}
set
{
_orderType = TranslateBackward(value);
}
}
}
Mapping:
<property name="_orderType" access="field" />
If you don't want to map the field directly (because you use a compile-safe mapping) you can map the public property using the access strategy "field", a naming strategy like "camelcase-underscore" and explicitly specify the "Int32" type.
you can always use enums for this kind of situation.
You can define it like this:
namespace MyApp.Domain
{
using System.ComponentModel;
public enum OrderType : short
{
[Description("Order Suspended")]
Suspended = 1,
[Description("Order Delivered")]
Delivered = 2,
[Description("Order New")]
Inserted = 3
}
}
and map it this way:
<property name="Type" type="MyApp.Domain.OrderType, MyApp.Domain" >
<column name="Type" not-null="true"/>
</property>
so you can write your QueryOver in a simple way like this:
var orders = this.Session.QueryOver<MyApp.Domain.Orders>()
.Where(x => x.Type == MyApp.Domain.OrderType.Inserted)
.List();
I want to get ClassA.ClassBCollection property filtered and paged. I need to change filtering dynamically.
The default querying will result in something like:
select * from ClassA
left outer join ClassB
on id == FK_ClassB
Can I customize querying of nhibernate set somehow?
Mappings:
<class name="ClassA">
<property name="Name" />
<set name="ClassBCollection">
<key column="FK_ClassB" on-delete="cascade" />
<one-to-many class="ClassB" />
</set>
</class>
<class name="ClassB">
<property name="Something"/>
</class>
If I do understand your question...
Can I customize querying of nhibernate set somehow?
...correctly, the answer is NO.
I mean, if you think about getting the instance of ClassA and doing some paging and filtering over its <set> collection. That would be always done in memory. (What we can do with mapping I appended at the end).
we can change the approach
In this case, when you need a filter and paging over the collection items, I would strongly recommend to go the other way. Create Criteria (QueryOver, HQL) not over the ClassA but over the ClassB.
First of all we have to extend ClassB mapping:
<class name="ClassB">
<property name="Something" />
<many-to-one name="ClassA" column="FK_ClassB" fetch="join" />
</class>
And then create a Criteria like this
var criteria = NHSession.Current.CreateCriteria<ClassB>();
criteria
.Add(new InExpression("ClassA", new object[] {1})) // id of one or more ClassA
.AddOrder(new Order("Something", true)) // Order By
.SetFirstResult(2) // Skip
.SetMaxResults(10); // Take
var list = criteria.List<ClassB>();
Because we used mapping of ClassA fetch="join" the resulting SQL statement will be very similar to the first snippet in this question.
So this way, we can achieve the desired SQL Select, but we cannot use ClassA.ClassBCollection directly. We did it this way...
NOTE:
Filters / paging which we can influence on the <set> mapping are static filter in the where clause and style of fetching values.
Where clause will always be evaluated when loading ClassBCollection as a property of the ClassA. It could be like where="IsActive=true"
In case that ClassA can have a lot of items in ClassBCollection, we can manage how they will be loaed. Very effective way is attribute batch-size documented here
Can I customize querying of nhibernate set somehow?
I'm not entirely sure what this means. If you meant, can I query and use WHERE clauses over the collection, the answer is yes. Here's how:
[TestFixture]
public class StackOverflowQuestion13496270Tests
{
public ISession session;
[SetUp]
public void SetUp()
{
session = // Get the current NHibernate session
}
[Test]
public void Query_ClassA()
{
var results = session.Query<ClassA>()
.Where( x => x.ClassBCollection.Any( y => y.Name == "Bob" ) )
.Fetch( x => x.ClassBCollection )
.Skip( 0 )
.Take( 50 )
.ToList();
}
[Test]
public void Query_ClassB()
{
var results = session.Query<ClassB>()
.Where( x => x.Name == "Bob" )
.Fetch( x => x.ClassAParent )
.Skip( 0 )
.Take( 50 )
.ToList();
}
public class ClassA
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<ClassB> ClassBCollection { get; set; }
}
public class ClassB
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
// Add this and the appropriate mapping modifications to be able to navigate back to the parent
public virtual ClassA ClassAParent { get; set; }
}
}
I have an entity where a composite id is used. I changed to code to make use of wrapping the composite id in a seperate key class. I expected that with Linq I could do a comparison on key object and with the Criteria API to use Restrictions.IdEq but both fail. I need to explicitly compare the key values to make it work.
I cannot find any documentation if this should work so for the moment I am stuck with direct comparisons but this means that when I alter the key that I also need to update the query code which is obviously not what I would want.
As a side note, I tried this with NHibernate 3.0.0 Alpha 2 and 3.
Domain
Mapping
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Cwc.Pulse.Dal"
namespace="Cwc.Pulse.Dal">
<class name="AddonStatus">
<composite-id name="Id">
<key-many-to-one name="Context" column="Context_Id" class="Context" />
<key-property name="AddonType" column="Addon_Id"/>
</composite-id>
<property name="Status" />
</class>
</hibernate-mapping>
Class
public class AddonStatus
{
public virtual string Status { get; set; }
public virtual Key Id { get; protected set; }
public AddonStatus()
{
Id = new Key();
}
public class Key
{
public virtual Context Context { get; set; }
public virtual AddonType AddonType { get; set; }
public override int GetHashCode()
{
return ContextId.GetHashCode() ^ AddonType.GetHashCode();
}
public override bool Equals(object obj)
{
if (this == obj) return true;
var o = obj as Key;
if (null == o) return false;
return Context == o.Context && AddonType == o.AddonType;
}
}
}
Working queries
The queries below work and as you can see I compare the key values explicitly. I do not compare the key object.
Linq
from status
in session.Query<AddonStatus>()
where status.Id.Context == context && status.Id.AddonType == addonType
select status
Criteria API
session.CreateCriteria<AddonStatus>()
.Add(Restrictions.Eq("Id.Context", context))
.Add(Restrictions.Eq("Id.AddonType", addonType))
Expected to work but dont
I expect the following queries to work. Either in efficiently for linq in memory instead of the database but I expect the criteria api to be smart enough to handle such composite id´s in queries.
Both linq and criteria api queries make use of a Key object comparison.
var key = new AddonStatus.Key
{
Context = context,
AddonType = addonType
};
Linq
from status
in session.Query<AddonStatus>()
where status.Id == key
select status
Criteria API
session.CreateCriteria<AddonStatus>()
.Add(Restrictions.IdEq(key))
So if anyone has such a scenario working then what am I doing wrong?
Not directly an answer to your question, but it may be useful to you anyway. You could avoid the (explicit) composite key by mapping the AddonStatus as composite-element on the owner (most probably the Context):
<class name="Context">
<map name="AddonStates" table="AddonStatus">
<key column="Context_Id" /> <!-- Foreign key to the Context -->
<index column="Addon_Id" /> <!-- Dictionary key -->
<composite-element>
<property name="Status" /> <!-- data -->
</composite-element>
</map>
</class>
In the class Context is looks like this:
class Context
{
IDictionary<AddonType, AddonStatus> AddonStates { get; private set; }
}
This results and pretty the same database structure, but it is different to work with. I can't say if this is what you actually want, but it just looks like it.
Interestingly, I'm getting almost the exact opposite of this behavior in 2.1.2.
My mapping (simplified):
<!-- Subscriber class -->
<class name="Subscriber" >
<composite-id name="SubscriberKey" class="SubscriberKey">
<key-property name="Request" column="RequestID" type="int"/>
<key-many-to-one name="User" column="UserID" class="User" not-found="ignore" />
</composite-id>
<!-- User class - note that this goes to a different schema,
and is not mutable. Who knows if that's important... -->
<class name="User" schema="AnotherDb.dbo" mutable="false">
<id name="Id" column="UserID" type="int">
<generator class="native" />
</id>
<property name="FirstName" column="FirstName" type="string" />
<property name="LastName" column="LastName" type="string" />
goes to:
public class User
{
public virtual int? Id {get; protected set;}
public virtual string FirstName { get; protected set; }
public virtual string LastName { get; protected set; }
public User() { }
}
public class Subscriber
{
public virtual SubscriberKey SubscriberKey { get; set; }
public virtual User User { get; set; }
public Subscriber() { }
}
public class SubscriberKey
{
public override bool Equals(object obj)
{
if (obj is SubscriberKey && obj != null)
return ((SubscriberKey)obj).Request == Request
&& ((SubscriberKey)obj).User.Id == User.Id;
return false;
}
public override int GetHashCode()
{
return (Request.ToString() + User.Id.ToString()).GetHashCode();
}
public virtual int Request { get; set; }
public virtual User User { get; set; }
public SubscriberKey() { }
}
Things which work:
CreateCriteria<Subscriber>()
.Add(Restrictions.IdEq(keyInstance))
.UniqueResult<Subscriber>();
CreateCriteria<Subscriber>()
.Add(Restrictions.Eq("SubscriberKey.User.Id", aUserID))
.Add(Restrictions.Eq("SubscriberKey.Request", aRequestID))
.UniqueResult<Subscriber>();
Things which don't work:
Get<Subscriber>(keyInstance);
I'm thinking this is an inconsistency between their various ID-equaling query forms. When I get time, I'll be building a minimal unit test to submit as a bug example. I'd be interested in any / all thoughts anyone might have on this...
edit: Heeey, I figured it out!
Things which do work, now that I've read this
Get<Subscriber>(new SubscriberKey() {
User = Load<User>(aUserID), // the important part!
Request = aRequestID
});
This will create a proxy object for the User key, without hitting the database (unless necessary). If you swap Load<User> for Get<User>, you'll immediately hit the database to populate the object, rather than respecting your lazy-loading properties. Use Load.
And things like this are precisely why people suggest the (type)Repository pattern - I can do this behind the scenes: Get<>(new SK(){User=Load<>(key.User.Id)}, and still Get(key) by a single key, identical to every other object.
I'm struggling to find the best way to model 1 : 0,1 relationships ("may have one" or "has at most one"). I believe this is called Z cardinality.
For example, suppose I have two classes Widget and WidgetTest. Not all Widgets are tested and the test is destructive so there can be at most one WidgetTest per Widget. Also assume that it's inappropriate to add the WidgetTest fields to Widget.
I would like my public interface to be:
Widget
WidgetTest { get; set; }
WidgetTest
Widget { get; }
Model 1: Widget has a WidgetTest property and in the database the Widget table has a uniquely constrained foreign key to WidgetTest. My DBA argues that this would allow a WidgetTest record to exist without a Widget.
WidgetTable
WidgetTestId (FK, UQ)
Model 2: Widget has a private collection of WidgetTest and enforces the 0,1 relationship by adding or removing a single object from the collection controlled by a public WidgetTest property. The database models this as 1:m with WidgetTest having a uniquely constrained foreign key to Widget. I argue that this means adopting the model to fit the database schema (i.e. more work for me).
WidgetTestTable
WidgetId (FK, UQ)
Which model is better? Which is easier to achieve with NHibernate? Or is there a third way?
Edit ... Here's what I ended up with:
public class Widget
{
// This is mapped in NH using a access strategy
private IList<WidgetTest> _widgetTests = new List<WidgetTest>(1);
public WidgetTest
{
get { return _widgetTests.FirstOrDefault(); }
set
{
_widgetTests.Clear();
if (value != null)
{
_widgetTests.Add(value);
}
}
}
}
My approach has been to model a one-to-many relationship in the mappings, but to constrain the "many" to a single item. This allows the optional one-to-one, and also guarantees that your WidgetTest instance is persisted when you save the Widget. For example:
public class Widget
{
/// <summary>
/// This property is ignored by the NHibernate mappings.
/// </summary>
public virtual WidgetTest WidgetTest { get; set; }
/// <summary>
/// For easier persistence with NHibernate, this property repackages the
/// WidgetTest property as a list containing a single item. If an
/// attempt is made to set this property to a list containing more than
/// one item, an exception will be thrown. But why bother? Just use the
/// WidgetTest property.
/// </summary>
public virtual IList<WidgetTest> WidgetTests
{
get
{
IList<WidgetTest> widgetTests = new List<WidgetTest>();
if (this.WidgetTest != null)
{
widgetTests.Add(this.WidgetTest);
}
return widgetTests;
}
set
{
if (value != null && value.Count > 1)
{
throw new Exception("The WidgetTests collection may not contain more than one item.");
}
else if (value != null && value.Count == 1)
{
this.WidgetTest = value[0];
}
else
{
this.WidgetTest = null;
}
}
}
}
When you say "assume that it's inappropriate to add the WidgetTest fields to Widget", do you mean in your domain objects or in the database. If you are happy for the fields to be in the same table in the database, how about mapping WidgetTest as a component of Widget? Have the NHibernate mapping file look like:
<class name="Widget" table="Widget">
...
<property name="WidgetProperty"/>
...
<component name="WidgetTest" class="WidgetTest">
<property name="WidgetTestProperty"/>
</component>
</class>
Giving the table structure:
WidgetTable
WidgetProperty
WidgetTestProperty
Which would still let you have the public interface you've specified, however, WidgetTest would become a value object which you may or may not want.
i have 2 other ideas here
Join table and map as Component
ignore Id of dependant class
The answer given by nw. can result in the exception "A collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance".
You will find this to be the case if you're using inverse="true" and cascade="all-delete-orphan" in your mapping file.
This is because nw.'s answer creates a new list every time the get accessor is called and doesn't do anything with the list passed in through the set accessor. As such, NHibernate doesn't have the IList<WidgetTest> reference it originally passed in when creating the object and can't proceed with the cascade.
So in order to fix this, we need to do something with that IList<WidgetTest> reference and be careful not to de-reference it.
public class Widget
{
public Widget()
{
_widgetTests = new List<WidgetTest>();
}
/// <summary>
/// This property is ignored by the NHibernate mappings.
/// </summary>
public WidgetTest WidgetTest { get; set; }
/// <summary>
/// For easier persistence with NHibernate, this property repackages the
/// WidgetTest property as a list containing a single item. If an
/// attempt is made to set this property to a list containing more than
/// one item, an exception will be thrown. But why bother? Just use the
/// WidgetTest property.
/// </summary>
private IList<WidgetTest> _widgetTests;
protected virtual IList<WidgetTest> WidgetTests
{
get
{
if (_widgetTests.Count == 0 && WidgetTest != null)
{
_widgetTests.Add(WidgetTest);
}
else if (_widgetTests.Count > 0 && WidgetTest == null)
{
_widgetTests.Clear();
}
else if (_widgetTests.Count > 0 && WidgetTest != _widgetTests[0])
{
_widgetTests.Clear();
_widgetTests.Add(WidgetTest);
}
return _widgetTests;
}
set
{
if (value != null && value.Count > 1)
{
throw new Exception("The WidgetTest collection may not contain more than one item.");
}
if (value != null && value.Count == 1)
{
WidgetTest = value[0];
}
else
{
WidgetTest = null;
}
//Store the reference
_widgetTests = value;
}
}
}
Mapping:
<class name="Widget" table="widgets">
...
<id name="Id" type="Guid" column="widgetId">
...
</id>
...
<bag name="WidgetTests" inverse="true" cascade="all-delete-orphan" access="property">
...
<key column="widgetId" />
<one-to-many class="WidgetTest" />
</bag>
</class>
Inspiration for the enhancement:
http://www.onkarjoshi.com/blog/188/hibernateexception-a-collection-with-cascade-all-delete-orphan-was-no-longer-referenced-by-the-owning-entity-instance/comment-page-1/
I have the following database schema:
http://lh4.ggpht.com/_SDci0Pf3tzU/SdM3XnAmmxI/AAAAAAAAEps/Ie3xW3ZVNfQ/s400/styleerror.png
And this is my mapping file:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="CodeSmithSampel.Generated.BusinessObjects" assembly="CodeSmithSampel">
<class name="CodeSmithSampel.Generated.BusinessObjects.Store, CodeSmithSampel" table="store" lazy="true">
<id name="Id" column="Id">
<generator class="native" />
</id>
<property name="Name" column="Name" />
<bag name="Employees" lazy="true" cascade="all-delete-orphan" inverse="true" >
<key column="Store_id"></key>
<one-to-many class="Employee"></one-to-many>
</bag>
<bag name="Products" table="storeproduct" lazy="true" cascade="all" inverse="true" >
<key column="Store_id"></key>
<many-to-many column="Product_id" class="Product" />
</bag>
</class>
</hibernate-mapping>
And ths is my Store entity class:
public partial class Store : BusinessBase<int>
{
#region Declarations
private string _name = String.Empty;
private IList<Employee> _employees = new List<Employee>();
private IList<Product> _products = new List<Product>();
#endregion
#region Constructors
public Store() { }
#endregion
#region Methods
public override int GetHashCode()
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append(this.GetType().FullName);
sb.Append(_name);
return sb.ToString().GetHashCode();
}
#endregion
#region Properties
public virtual string Name
{
get { return _name; }
set
{
OnNameChanging();
_name = value;
OnNameChanged();
}
}
partial void OnNameChanging();
partial void OnNameChanged();
public virtual IList<Employee> Employees
{
get { return _employees; }
set
{
OnEmployeesChanging();
_employees = value;
OnEmployeesChanged();
}
}
partial void OnEmployeesChanging();
partial void OnEmployeesChanged();
public virtual IList<Product> Products
{
get { return _products; }
set
{
OnProductsChanging();
_products = value;
OnProductsChanged();
}
}
partial void OnProductsChanging();
partial void OnProductsChanged();
#endregion
}
The product class:
public partial class Product : BusinessBase<int>
{
#region Declarations
private float _price = default(Single);
private string _name = null;
private IList<Store> _stores = new List<Store>();
#endregion
#region Constructors
public Product() { }
#endregion
#region Methods
public override int GetHashCode()
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append(this.GetType().FullName);
sb.Append(_price);
sb.Append(_name);
return sb.ToString().GetHashCode();
}
#endregion
#region Properties
public virtual float Price
{
get { return _price; }
set
{
OnPriceChanging();
_price = value;
OnPriceChanged();
}
}
partial void OnPriceChanging();
partial void OnPriceChanged();
public virtual string Name
{
get { return _name; }
set
{
OnNameChanging();
_name = value;
OnNameChanged();
}
}
partial void OnNameChanging();
partial void OnNameChanged();
public virtual IList<Store> Stores
{
get { return _stores; }
set
{
OnStoresChanging();
_stores = value;
OnStoresChanged();
}
}
partial void OnStoresChanging();
partial void OnStoresChanged();
#endregion
}
The mapping for the Product class:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="CodeSmithSampel.Generated.BusinessObjects" assembly="CodeSmithSampel">
<class name="CodeSmithSampel.Generated.BusinessObjects.Product, CodeSmithSampel" table="product" lazy="true">
<id name="Id" column="Id">
<generator class="native" />
</id>
<property name="Price" column="Price" />
<property name="Name" column="Name" />
<bag name="Stores" table="storeproduct" lazy="true" cascade="all" inverse="true" >
<key column="Product_id"></key>
<many-to-many column="Store_id" class="Store" />
</bag>
</class>
</hibernate-mapping>
What is particularly weird is that when I add a Store object to one of the product, the database record is not updated; the add doesn't seem to take place, although the new store object exists in the database:
IManagerFactory managerFactory = new ManagerFactory();
var productManager = managerFactory.GetProductManager();
var myProduct= productManager.GetById(2);
var myStore = new Store();
myStore.Name = "new Store"; //a "new store" entry is created in the Store table
myProduct.Stores.Add(myStore); // but this "new store" is not linked to the myproduct, as it should.
productManager.Session.CommitChanges();
Is there anything I miss?
Note: I generate the above code using CodeSmith.
Edit: The accepted answer works. The reason I got in this problem is because
Only one entity class should have inverse = true, not two. So either Product or Store should set the inverse to false. The code generation tool didn't handle this properly.
The correct way to Add Many to Many relationship is explained below. You must add two times.
Can this have anything to do with the fact that you have a surrogate key in the storeproducts table ?
What happens if you remove this surrogate key column Id, and put the primary key on the combination of the product_id and store_id columns ?
I believe that, if you want to have a surrogate key on the storeproducts table, you'll have to create yet another entity.
If you want to use the surrogate key, you'll have to use the idbag mapping.
How does your Product class and mapping look like ?
I see that you specify the 'inverse' attribute in your mapping of the Products collection in the Store entity.
If you do this (and thus you have a bi-directional association), then you should add the Store to the Stores collection of the product as well.
Since -from the NH documentation- :
Changes made only to the inverse end
of the association are not persisted.
This means that NHibernate has two
representations in memory for every
bidirectional association, one link
from A to B and another link from B to
A. This is easier to understand if you
think about the .NET object model and
how we create a many-to-many
relationship in C#:
category.Items.Add(item); // The category now "knows" about the relationship
item.Categories.Add(category); // The item now "knows" about the relationship
session.Update(item); // No effect, nothing will be saved!
session.Update(category); // The relationship will be saved
The non-inverse side is used to save
the in-memory representation to the
database. We would get an unneccessary
INSERT/UPDATE and probably even a
foreign key violation if both would
trigger changes! The same is of course
also true for bidirectional
one-to-many associations.
You may map a bidirectional
one-to-many association by mapping a
one-to-many association to the same
table column(s) as a many-to-one
association and declaring the
many-valued end inverse="true".
This means, that only one of the ends should be inverse.
Adding a Product to a store, should be done like this:
public class Store
{
public void AddProduct( Product p )
{
if( _products.Contains (p) == false )
{
_products.Add (p);
p.AddStore(this);
}
}
}
public class Product
{
public void AddStore( Store s )
{
if( _stores.Contains (s) == false )
{
_stores.Add (s);
s.AddProduct(this);
}
}
}
(Very important to check whether the collection already contains the item to be added; otherwise you'll end up in an infinite loop.