Hello guys.
This time i'll try to give all the info on what i believe is a bug on how nhibernate works (again, it might also be that i'm completly lost in how it works, but that's why i'm posting it here).
lets start with the database. I have the following tables (SQL Server 2005 express):
CREATE TABLE [dbo].[Aluno](
[IdAluno] [int] IDENTITY(1,1) NOT NULL,
[Nome] [varchar](50) COLLATE Latin1_General_CI_AS NOT NULL,
CONSTRAINT [PK_Aluno] PRIMARY KEY CLUSTERED
(
[IdAluno] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[Disciplina](
[IdDisciplina] [int] IDENTITY(1,1) NOT NULL,
[IdAluno] [int] NOT NULL,
[Nome] [varchar](50) COLLATE Latin1_General_CI_AS NOT NULL,
CONSTRAINT [PK_Disciplina] PRIMARY KEY CLUSTERED
(
[IdDisciplina] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
The idea is that each row on the table Aluno (which, btw, means Student in english) has 0 or more entries on the table Disciplina (i believe that the translation of Disciplina is subject).
My classes look like this:
namespace NHibernateTests
{
public class Subject
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
}
namespace NHibernateTests
{
public class Student
{
private int _id;
private string _name;
private IList<Subject> _subjects = new List<Subject>();
public int Id
{
get { return _id; }
set { _id = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public IList<Subject> Subjects
{
get { return _subjects; }
set { _subjects = value; }
}
}
}
Nothing too fancy (btw, in the real word i'd return a readonly collection from the subjects property, but in this demo i'm just returning the _subjects field).
In my case, Subject is really a component and i don't intend to follow the life time of each instance of that class (this was a decision i've made since i needed to model a one-to-many relation between an entity - in this case, Student - and one or more components).
So, after reading the existing docs on the nhibernate site, I've ended up with the following hbm file:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping
xmlns="urn:nhibernate-mapping-2.2"
schema="dbo">
<class name="NHibernateTests.Student,NHibernateTests"
table="Aluno" lazy="false">
<id name="Id" column="IdAluno" unsaved-value="0">
<generator class="identity" />
</id>
<property name="Name" column="Nome" />
<bag name="Subjects" cascade="all" lazy="false" table="Disciplina" >
<key column="IdAluno" />
<composite-element class="NHibernateTests.Subject,NHibernateTests">
<property name="Name" column="Nome" />
</composite-element>
</bag>
</class>
</hibernate-mapping>
ok, maybe i got it wrong, but to me, this file says something like this:
"Each instance of type studend is persisted on the Aluno table. It has a Name property, which is mapped to column Nome in that table. There's also a collection of elements of type Subject maintained on the Subjects property of the object. Each element in this collection is a component (that is what i think the <composite-element> is for) and each should be persisted on the Disciplina table. The relation between the Disciplina table and the "main" Aluno table is performed through the IdAluno field - which gets its value from the Id element of the Student class"
first thing: is this description completly wrong?
Ok, supposing that it's correct, then i want to create a new student with one or more subjects and persist it to the database by using a transaction. here's the code I've tried to run to achieve that:
//auxiliary method that creates a new subject
private static Subject AddSubject(string p)
{
Subject sub = new Subject();
sub.Name = p;
return sub;
}
static void Main(string[] args)
{
Configuration cfg = new Configuration();
cfg.Configure(DB_PATH); //path to config file
ISessionFactory factory = cfg.BuildSessionFactory();
ISession session = factory.OpenSession();
Student std = new Student();
std.Name = "Luis";
std.Subjects.Add(AddSubject("Port"));
std.Subjects.Add(AddSubject("Eng"));
ITransaction tran = session.BeginTransaction();
session.Save(std);
tran.Rollback();
session.Flush();
}
Ok, i start by getting the necessary info for creating an ISession. Then, a new "dummy" student is created with 2 subjects and i call the Save method to indicate my willingness to save the new object that i've just created.
Now, instead of commiting the transaction, i roll it back and flush the session. My question is: what should happen after flushing the data?
Since i really don't know nhibernate, i might be completly wrong, but i expected to see 0 rows in both tables. Unfortunately, the subject objects maintained by the std objects are inserted during the flush method call.
i really expected to see the SQL instructions for inserting items on the table Aluno and Disciplina written on the log after calling the Save method. Unfortunately, nhibernate only generates the sql for inserting items on the Aluno table and even though it shows it's trying to cascade the insert, there really isn't any entry regarding any of the instances maintained on the Subjects proeprty.
Interestingly, when i call flush, it seems like it recreates the elements on the collection and only then it persits those instances to the database, leaving me with 2 entries on the Disciplina table and none on the Aluno table (note that this is possible because i didn't explicitly set the IAluno on table Disciplina as a foreign key).
Now, I thought this was a bug and opened an entry onthe issue tracker, which was quickly closed by saying that "you don't know how nhibernate works". Yes, this is correct, but are you saying that a save operation made over an object in this scenario shouldn't be cascaded to each component maintained on a list that is owned by that object? This can't be true.
for instance, imagine a test. in that case, before calling rollback, i'd get a reference to the connectio object used by nhibernate and i'd perform a select over the table to get the element that has been added. if the behavior i've described here is the "correct" behavior (which i really don't think it is) then that means that i'd get the "temporary" student object but wouldn't be able to get any subject from the Disciplina table since it wasn't really inserted to the db.
in fact, even if you don't think about the test, this can't be the correct behavior. if it is, then what's the purpose of the transaction?
btw, i've transformed my subject class in an entity with bi-directional relation and in that case, cascading works as expected.
so, can anyone tell me why this works like this?
thanks!
P.S.: there's a sample on the issue tracker with code that shows this problem
http://jira.nhibernate.org/browse/NH-1141