Angel \”Java\” Lopez on Blog

May 28, 2011

NHibernate 3 (Part 7) One-To-Many with Inverse

Filed under: .NET, C Sharp, NHibernate — ajlopez @ 11:40 am

Previous Post
Next Post

The previous example had a problem: it use one INSERT AND one UPDATE for each chapter. In this post, I use an alternative way to mapping a one-to-many list, using the inverse attribute. As usual, the code is at my AjCodeKata Code Project, under trunk/NHibernate/BookChapters. You can download a “frozen” version from NHibernate3BookChaptersInverse.zip.

I modified the previous solution, adding a new Books.Inverse console project, copying the original Books.Console. And I added a new attribute in Book mapping:

<list name="Chapters" cascade="all-delete-orphan" 
  inverse="true">
  <key column="BookId"/>
  <index column="ChapterIndex"/>
  <one-to-many class="Chapter"/>
</list>

The new attribute is inverse=”true”. With this clue, NHibernate knows that the management of the list and its items are under programmer control. What does it mean? I ran the new console program: each chapter is saved with only ONE insert, but the result in the database was:

No BookId, no ChapterIndex were set! The problem is in the code:

cookbook.Chapters.Add(new Chapter() { Title = "Models and Mappings" });
cookbook.Chapters.Add(new Chapter() { Title = "Configuration and Schema" });
cookbook.Chapters.Add(new Chapter() { Title = "Sessions and Transactions" });

When I created a new Chapter, I didn't set any Book or Chapter Index information. With inverse=”true”, now that is my responsibility. Then, I changed the Chapter class, adding a new property:

// Used in Inverse example
public virtual int ChapterIndex { get; set; }

And I changed the chapter creation code to:

cookbook.Chapters.Add(new Chapter() { Title = "Models and Mappings", Book = cookbook, ChapterIndex = 0 });
cookbook.Chapters.Add(new Chapter() { Title = "Configuration and Schema", Book = cookbook, ChapterIndex = 1 });
cookbook.Chapters.Add(new Chapter() { Title = "Sessions and Transactions", Book = cookbook, ChapterIndex = 2 });

Now, the output of the insert is:

NHibernate: INSERT INTO Books (Title, Author, Id) VALUES (@p0, @p1, @p2);@p0 = 
'NHibernate Cookbook' [Type: String (4000)], @p1 = 'Jason Dentler' 
[Type: String (4000)], @p2 = b643aa0e-1917-4066-96a4-64a09562a58a [Type: Guid (0)]

NHibernate: INSERT INTO Chapters (Title, Notes, BookId, ChapterIndex, Id) VALUES
 (@p0, @p1, @p2, @p3, @p4);@p0 = 'Models and Mappings' [Type: String (4000)], @p1 
 = NULL [Type: String (4000)], @p2 = b643aa0e-1917-4066-96a4-64a09562a58a
 [Type : Guid (0)], @p3 = 0 [Type: Int32 (0)], @p4 = 
 df9114af-7183-4b1b-9826-d12982a898a5 [Type: Guid (0)]
 
NHibernate: INSERT INTO Chapters (Title, Notes, BookId, ChapterIndex, Id) VALUES
 (@p0, @p1, @p2, @p3, @p4);@p0 = 'Configuration and Schema' 
 [Type: String (4000)], @p1 = NULL [Type: String (4000)], @p2 =
 b643aa0e-1917-4066-96a4-64a09562a58a [Type: Guid (0)], @p3 = 1 [Type: Int32 (0)],
 @p4 = 3fc1df36-c7de-4ff8-add2-8f843b627115 [Type: Guid (0)]

NHibernate: INSERT INTO Chapters (Title, Notes, BookId, ChapterIndex, Id) VALUES
 (@p0, @p1, @p2, @p3, @p4);@p0 = 'Sessions and Transactions' 
 [Type: String (4000)], @p1 = NULL [Type: String (4000)], @p2 = 
 b643aa0e-1917-4066-96a4-64a09562a58a  [Type: Guid (0)], @p3 = 2 [Type: Int32 (0)],
 @p4 = 9c8891e4-6419-4469-aa3a-154f2c908985 [Type: Guid (0)]

Chapters are saved as:

All Ok! Note that I have not to add the chapters to the NHibernate session: they are saved as part of the saved book. But after inverse=true, I should set the inverse relation (Chapter to Book) and order properties.

Next steps: explore deletion, update, more complex mapping, logging options, alternative mappings (Fluent NHibernate, ConfORM, new NHibernate 3.2 mapping by code).

Keep tuned!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

May 25, 2011

Code Generation and Artificial Intelligence

In late seventies and early eighties (past century ;-)) I was learning a lot about programming, programming languages and computer science in general. They were the days of COBOL, BCPL, IBM/360 assembler languages, JCL, punched cards, Pick Operating Systems, Pick Basic, ALGOL/W, Fortran, PL/I, and C (in CP/M, DOS, Unix/Xenix world). One of my preferred topics (then and now) was Artificial Intelligence, a broad term. Those days were the golden age of expert systems. I was writing a lot of C code, then, as a lazy programmer, I started to write my own C utilities to generate more C programs ;-). Since then, I have an idea:

Code Generation should be an Artificial Intelligence application

In the late eighties, early nineties, the amount of code to write to have a professional application diminished (new IDEs, wizards). But that was a transitory state: the complexity imposed by market, new customer requirements, online application, concurrency, new technologies and languages (Windows, GUIs…) all conspired to the increase of effort that should be employed to create any non-trivial application. I embraced Java at mid-90s, and .NET at 2001. Large class libraries, new tips and tricks, architectural styles, patterns, JSP, JSF, MVC, ASP.NET, Javascript, some PHP, distributed computing, physical tiers… The software development arena became more complex. And now, we have mobile, Android, Windows Phone 7, ASP.NET MVC, new JDKs, JVMs, new dynamic languages… I could continue this enumeration for ever, no break here ;-)

So, at the begin of the century, I decided to start my open source project for code generation, AjGenesis. First, this is the current process I use in my code generation projects:

(Ok, I’m a developer, not a designer ;-))

The staring models (left) are free: you can use any model you want, it’s not fixed. This is a key decision: I want YOU have the freedom to start whit whatever you want as a base for your code generation. But it should be a MODEL. It’s not only code generation: it’s CODE GENERATION FROM A MODEL(S). Usually, I have an abstract model, like:

<Project>
	<Name>AjApp</Name>
	<Description>Building an Application using AjGenesis</Description>
	<Model>
		<Entities>
			<Entity Source="Entities/Customer.xml"/>
			<Entity Source="Entities/Supplier.xml"/>
		</Entities>
	</Model>
</Project>

and one or more technology model, like:

<Technology>
	<Programming>
		<Dialect>CSharp4NhMvc</Dialect>
	</Programming>
	<Database>
		<Dialect>MsSql</Dialect>
		<Name>AjTestNh</Name>
		<Host>.\SQLEXPRESS</Host>
	</Database>
	<NHibernate>
		<Dialect>NHibernate.Dialect.MsSql2000Dialect</Dialect>
	</NHibernate>
</Technology>

(more info in my AjGenesis posts). The models can be written using XML or text. Or you can use your own models (see Models for Code Generation in AjGenesis). The text artifacts could be Java, C#, VB.NET, Ruby, whatever you want. Notably: my AjGenesis examples started generating PHP, and JSP 1.x, and they evolved WITHOUT CHANGING THE TOOL to generate PHP (new versions), Java, JSP 2.x, Hibernate, NHibernate, ASP.NET 1.x, ASP.NET 2.x, ASP.NET MVC… It can be adapted to any text-based technology. To have such flexibility, it has no GUI IDE association. It's has Spartan interface, but I liked it.

Since inception, I adopted a general dynamic language, AjBasic, to support tasks and templates: I didn't want a transformation engine, from model to code via templates. I want to have the power of a GENERAL language. Why? TO SUPPORT EXTENSIBILITY, beyond template expansion. And, surprise! What kind of extensibility? Anyone, but you know…. yes, right! Artificial Intelligence!

My assertion:

Code Generation from a Model can be implemented as an Expert System, and then, it's a clear target for Artificial Intelligence

Graphically:

You can view software developers as experts. We can distill their expertise in expert systems and add them to code generation process. Some examples:

- An expert system can use a rule engine to make decisions: macro or mini ones. Mini-one: “This web report can have lot of rows, I need pagination, filtering, and ordering….” And then, some part of the expert systems recommends: “Yes! We could use JQuery and JQuery.UI, client-side filtering”… and other part (yes, my expert systems is multi-mind ;-)) says “No! We need server-side filtering, then we need such queries and services, and Ajax endpoints and … “ Got the picture?

A micro-decision I employed last year in a real project: one kind of entity, in an abstract model, was marked as “massive” meaning: “we have lot of instances of Entity X”. Then, when in the UI we needed to select one Entity X, instead of a dropdown, the code generation engine put a search box, with auto-complete, with Ajax service at server.

I could use, for the abstract model, some base ontology and make assumptions and decisions. Example:

- Every entity could be: a person, a company, a place, a physical good, an event, a document. Then, the expert system could infer: a place has an address. A physical good should be located in a place. A document X should be needed to represent the move of a physical good from place A to place B. A person can be an employee. Every employee has social security… etc…. etc.. Then, we need a report of the X events, ordered by date, grouped by location, etc… etc… This document has a money total, it should be interesting to have a total grouped by any party (customer? company? person?) involved in the document. And by month? Yes, we need a pie chart … and so on… ;-)

I could add a rule engine, to make some of these kind of decisions, with rules like “if entity X is massive and the UI is web and JQuery is an available option then … “. Or rules like: “if entity X inherits from entity Y and we need persistence, we adopt table-per-hierarchy (or table-per-concrete class) ORM strategy, and then we generate…. database tables and …. ORM mapping code or configuration”. Or: “this entity X is a document having a date, we add an index on that date/time column in Y table”. So many decisions can be made in this way. Actually, in AjGenesis, most of these decisions are in the tasks and technology models.

In the long term, code generation from a model involves:

- This is the starting point: abstract models, technical models (restrictions: “we need all this in ASP.NET MVC 3”)

- This is the expected deliverable (a partial working system)

- And these are the distilled expert knowledge we got from our/others experience.  And with all these, we can go from start to end. using the accumulated programming skills, ORM configuration, dependency injection libraries, UI tips and tricks, recurrent patterns, database creation knowledge, etc..

Software development and architecture is related to:

- Know what you need

- Know what are the problems to resolve (persistence, concurrency, security, traceability, UI access, etc…)

- Know what are the available solutions (database, NoSql, language, class libraries, frameworks, UI technology, patterns…)

Code generation from a model has a (great, IMO) benefit: it put clear the existence, separation and relations of the above points.

Other way to explore: the use of expert agents. What does “expert agent” mean in my jargon? My vision: you can have modules, agents that can be added, removed without much effort, that can resolves problems. Then, someone can develop a JSF expert, that knows what JavaBeans to define for implement a given interface in a use case. Then, if in the future the rule engine or the initial restrictions say: “Now, we need JSF 17.2, or ASP.NET MVC 6.5 or the new SuperDuperOpenSourceUIThatIsTheNextBigThingHere”, we can adopt new solutions for the similar problems, distilling the new knowledge and putting it in a new expert agent.

Ok, all these are the reasons for an (upcoming?) AjRules ;-). And the justification for AjLisp (see here) and some AjProlog to be updated (see it inside AjCodeKatas).

Related posts (in Spanish):

Models, decisions, Artificial Intelligence and AjGenesis

About Code Generation

Keep tuned!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

May 19, 2011

NHibernate 3 (Part 6) One-To-Many with Many-To-One

Filed under: .NET, C Sharp, NHibernate — ajlopez @ 11:11 am

Previous post
Next post

The database is the same from the previous example (the same name and scripts):

But now, I want to have a reference, in the domain, from chapter to book:

public class Chapter
{
    public virtual Guid Id { get; set; }
    public virtual string Title { get; set; }
    public virtual string Notes { get; set; }
	
	// New property
    public virtual Book Book { get; set; } 
}

So, I changed the Chapter mapping to:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Books"
namespace="Books">
  <class name="Chapter" table="Chapters">
    <id name="Id">
      <generator class="guid" />
    </id>
    <property name="Title" not-null="true" />
    <property name="Notes" />
    <many-to-one name="Book" column="BookId" />
  </class>
</hibernate-mapping>

The new element is many-to-one. The Book mapping has no change:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Books"
namespace="Books">
  <class name="Book" table="Books">
    <id name="Id">
      <generator class="guid" />
    </id>
    <property name="Title" not-null="true" />
    <property name="Author" not-null="true"/>
    <list name="Chapters" cascade="all-delete-orphan">
      <key column="BookId"/>
      <index column="ChapterIndex"/>
      <one-to-many class="Chapter"/>
    </list>
  </class>
</hibernate-mapping>

I added the “show_sql” in the configuration file, NHibernate section:

  <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
    <session-factory>
      <property name="dialect">NHibernate.Dialect.MsSql2000Dialect</property>
      <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
      <property name="connection.connection_string">Data Source=.\SQLEXPRESS;Initial Catalog=NHibernate3BooksOneToMany;Integrated Security=True</property>
      <property name="proxyfactory.factory_class">
        NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle
      </property>
     <property name="show_sql">true</property>
      <mapping assembly="Books.Console" />
    </session-factory>
  </hibernate-configuration>

This property is very useful when you are learning about NHibernate: it dumps in console the SQL commands that NHibernate executes during its operation. This is the main console program:

ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory();
using (ISession session = sessionFactory.OpenSession())
{
    using (ITransaction tx = session.BeginTransaction())
    {
        Book cookbook = new Book()
        {
            Title = "NHibernate Cookbook",
            Author = "Jason Dentler",
            Chapters = new List<Chapter>()
        };
        cookbook.Chapters.Add(new Chapter() { Title = "Models and Mappings" });
        cookbook.Chapters.Add(new Chapter() { Title = "Configuration and Schema" });
        cookbook.Chapters.Add(new Chapter() { Title = "Sessions and Transactions" });
        session.Save(cookbook);
        tx.Commit();
        session.Close();
    }
}
using (ISession session = sessionFactory.OpenSession())
{
    foreach (Book book in session.Query<Book>().Fetch(b => b.Chapters))
        {
            System.Console.WriteLine(string.Format("Book {0}", book.Title));
            int nchapter = 0;
            foreach (Chapter chapter in book.Chapters)
            {
                System.Console.WriteLine(string.Format("Chapter {0}:{1}", ++nchapter, chapter.Title));
                System.Console.WriteLine(string.Format("From Book {0}", chapter.Book.Title));
            }
        }
}
System.Console.ReadKey();

A new method is invoke: .Fetch. I'll explain it below. Now, run the program, and examine the console output. First:

NHibernate: INSERT INTO Books (Title, Author,
 Id) VALUES (@p0, @p1, @p2);@p0 = 'NHibernate Cookbook' [Type: String (4000)], @p1 = 
'Jason Dentler' [Type: String (4000)], 
@p2 = 20d05308-aea1-49f2-9b4a-7441e73dab4e [Type: Guid (0)]

This is the command that inserts the new book. Then:

NHibernate: INSERT INTO Chapters (Title, Notes, BookId, Id) VALUES (@p0, @p1, @p 
2, @p3);@p0 = 'Models and Mappings' [Type: String (4000)], @p1 = NULL [Type: 
String (4000)], @p2 = NULL [Type: Guid (0)], @p3 = 
15ce4169-08ef-4f75-8493-8fe68da7e918 [Type: Guid (0)] 
NHibernate: INSERT INTO Chapters (Title, Notes, BookId, Id) VALUES (@p0, @p1, @p2, @p3);
@p0 = 'Configuration and Schema' [Type: String (4000)], @p1 = NULL [Type: String (4000)],
 @p2 = NULL [Type: Guid (0)], @p3 = 3edb510d-4cbd-44fd-b4a1-094a258ec07f [Type: Guid (0)] 
NHibernate: INSERT INTO Chapters (Title, Notes, BookId, Id) VALUES (@p0, @p1, @p2,
 @p3);@p0 = 'Sessions and Transactions' [Type: String (4000)], @p1 = NULL [Type: String (4000)], @p2 = NULL [Type: Guid (0)], @p3 = af025d1b-cdc6-4c8f-9014-71c5dee135e1 [Type: Guid (0)]

These commands insert the three chapters. Hey! They ARE NOT setting the BookId (note @p2 = NULL) and no mention of ChapterIndex. Curiously, NHibernate set those columns IN A SECOND PASS:

NHibernate: UPDATE Chapters SET BookId = @p0, ChapterIndex = @p1 WHERE Id = @p2; 
@p0 = 20d05308-aea1-49f2-9b4a-7441e73dab4e [Type: Guid (0)], @p1 = 0 [Type: 
Int32 (0)], @p2 = 15ce4169-08ef-4f75-8493-8fe68da7e918 [Type: Guid (0)] 
NHibernate: UPDATE Chapters SET BookId = @p0, ChapterIndex = @p1 WHERE Id = @p2; 
@p0 = 20d05308-aea1-49f2-9b4a-7441e73dab4e [Type: Guid (0)], @p1 = 1 [Type: 
Int32 (0)], @p2 = 3edb510d-4cbd-44fd-b4a1-094a258ec07f [Type: Guid (0)] 
NHibernate: UPDATE Chapters SET BookId = @p0, ChapterIndex = @p1 WHERE Id = @p2; 
@p0 = 20d05308-aea1-49f2-9b4a-7441e73dab4e [Type: Guid (0)], @p1 = 2 [Type: 
Int32 (0)], @p2 = af025d1b-cdc6-4c8f-9014-71c5dee135e1 [Type: Guid (0)]

This is weird (and it's the reason why we have to define ChapterIndex and BookId as nullable, as I mentioned in the previous example). I would expect NHibernate save in one pass the Book+Chapters. I don't know why it is the “natural” behavior, and is one of the many behaviors we have to learn in order to master NHibernate. Note that usefulness of show_sql: there are other options to log NHibernate (notable, log4net logging), but this is a simple property you can use now in your initial programs. We should improve this behavior in an upcoming post.

This is the select statement:

NHibernate: select book0_.Id as Id0_0_, chapters1_.Id as Id1_1_, book0_.Title as 
Title0_0_, book0_.Author as Author0_0_, chapters1_.Title as Title1_1_, chapters 
1_.Notes as Notes1_1_, chapters1_.BookId as BookId1_1_, chapters1_.BookId as Boo 
kId0__, chapters1_.Id as Id0__, chapters1_.ChapterIndex as ChapterI5_0__ from Bo 
oks book0_ left outer join Chapters chapters1_ on book0_.Id=chapters1_.BookId

Surprise: The select retrieves Books AND Chapters, using an outer join. In this way, NHibernate can fill the chapters of each book. But this behavior is not the “default” one: the chapters are not loaded every time you retrieves a book. The NHibernate default behavior is to load the Chapters list ONLY when you iterate over it. If you don't use the list, the chapters are not loaded. The above statement (and the adding of the join) is a consequence of using the Fetch:

foreach (Book book in session.Query<Book>().Fetch(b => b.Chapters))

That additional method says: “Hey, NHibernate! Go and get the book, but the chapters too!”. You can experiment, and remove the Fetch method. Then, this is the partial output (another data set):

NHibernate: select book0_.Id as Id0_, book0_.Title as Title0_, book0_.Author as 
Author0_ from Books book0_ 
Book NHibernate Cookbook 
NHibernate: SELECT chapters0_.BookId as BookId1_, chapters0_.Id as Id1_, chapter 
s0_.ChapterIndex as ChapterI5_1_, chapters0_.Id as Id1_0_, chapters0_.Title as Title1_0_,
 chapters0_.Notes as Notes1_0_, chapters0_.BookId as BookId1_0_ FROM Chapters
 chapters0_ WHERE chapters0_.BookId=@p0;@p0 = 
cfb043eb-6ffc-4727-a5ad-05afe867ab57 [Type: Guid (0)] 
Chapter 1:Models and Mappings 
From Book NHibernate Cookbook 
Chapter 2:Configuration and Schema 
From Book NHibernate Cookbook 
Chapter 3:Sessions and Transactions 
From Book NHibernate Cookbook

There is a select for the book, and then, FOR EACH BOOK (trust me ;-) there is a new select for its chapter. Again: another “behavior” to have into account in order to have a program that doesn't abuse of database commands. Note that the output:

Book NHibernate Cookbook

PRECEDES the select of the chapters. Until the program iterates the chapter list, NO SELECT is performed. Another point: chapter.Book is not null. Thanks to NHibernate, the reference from Chapter to its parent Book is automatically filled.

So, this is the reason for the added .Fetch method. We have to explore alternatives to circumvent the pair INSERT-UPDATE for each chapter, topic for the next post.

As usual, the code is at my AjCodeKata Code Project, under trunk/NHibernate/BookChapters. You can download a “frozen” version from NHibernate3BookChapters.zip.

Next steps: explore inverse=”true”, other logging options, many-to-many, session cache, etc.

Keep tuned!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

May 18, 2011

AjTalk: Implementing an Smalltalk-like interpreter (Part 1) Object structure

Filed under: .NET, AjTalk, Open Source Projects, Programming Languages, Smalltalk — ajlopez @ 10:59 am

I start writing about the design decisions I had to take to build AjTalk, my open source Smalltalk-like interpreter. There is a long tradition in building Smalltalk virtual machines; let see what I could do and how.

In this first post I want to explore how to implement the core concept of any compiler or interpreter that tries to use an Smalltalk-like object.
An object in classic Smalltalk has an internal state, represented by instance variables (leave aside the indexed variables). A first view:

In many implementations (including the early ones, like the classic Smalltalk-80), each object contains values ??of one type: either it is an object containing single-byte or single words, or just pointers to other objects:

It is also usual that some values??, rather than pointers, ??are "primitive" values as an integer. To get the difference between a pointer to another object and an integer value, some bits of the word that stores the pointer / integer value are used to distinguish between the two kinds. A pointer can points to another object:

Another implementation style, is to use an object table instead of a direct pointer:

A table of objects has, per cell, representing object information, such as type (an object of bytes? of pointers?), its class, and the location of its internal state (instance variable values). The object table strategy simplifies operations like “become:” and the garbage collection. It can be used to remove the object from memory and retrieve it when a message is send to it. The cell had the persistence information.

How do I implemented all these features in AjTalk? From the beginning I wanted AjTalk could access and manage .NET native objects. So I chose to have two types of objects: the natives .NET objects (strings, arrays, integers, reals, any framework object…), and the AjTalk proper objects. So instead of handling objects that contain only bytes or chars, or pointers, I decided that AjTalk objects have instance variables whose values ??point to any object (.NET or AjTalk).

Internally, an AjTalk object keep an array of object values?:

[Serializable]
public class BaseObject : IObject, ISerializable
{
    private IBehavior behavior;
    private object[] variables; // pointers to objects
    // ...
}


An element of the array can point to an integer, a char, ie, value objects, or to objects in memory. .NET achieves this using boxing, the price to pay to get this flexibility. My implementation use the native mechanism of boxing/unboxing. Fortunately, the implementation details are directly managed by. NET. From the point of view of my C # code, all this management is totally transparent. This solution delegates the management of garbage collection (freeing the memory of unreferenced objects, unreachable from the active object graph) to the .NET implementation.

Next steps: explore how to implement message and their invocation, methods implementation, classes and inheritance, and more.

Keep tuned!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

May 9, 2011

NHibernate 3 (Part 5) First One-To-Many Mapping

Filed under: .NET, C Sharp, NHibernate — ajlopez @ 9:57 am

Previous post
Next post

This time, I want to map this data model:

Each book can have zero, one or more chapters. This is the class diagram for my domain:

This is the code for Book.cs:

public class Book
{
    public virtual Guid Id { get; set; }
    public virtual string Title { get; set; }
    public virtual string Author { get; set; }
    public virtual IList<Chapter> Chapters { get; set; }
}

As usual, the properties are virtual (I should write why, in upcoming post). And note, Chapters is a IList<Chapter>. The IList will be loaded by NHibernate. The Chapter.cs class:

public class Chapter
{
    public virtual Guid Id { get; set; }
    public virtual string Title { get; set; }
    public virtual string Notes { get; set; }
}

The book mapping has a new element:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Books"
namespace="Books">
  <class name="Book" table="Books">
    <id name="Id">
      <generator class="guid.comb" />
    </id>
    <property name="Title" not-null="true" />
    <property name="Author" not-null="true"/>
    <list name="Chapters" cascade="all-delete-orphan">
      <key column="BookId"/>
      <index column="ChapterIndex"/>
      <one-to-many class="Chapter"/>
    </list>
  </class>
</hibernate-mapping>

The <list> element points to the Book property Chapters. The elements will be ordered based on the table column ChapterIndex. BookId and ChapterIndex are not properties in Chapter class. They are columns in Chapter table. The Chapter mapping has no reference to Book class:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Books"
namespace="Books">
  <class name="Chapter" table="Chapters">
    <id name="Id">
      <generator class="guid.comb" />
    </id>
    <property name="Title" not-null="true" />
    <property name="Notes" />
  </class>
</hibernate-mapping>

What does the “cascade=all-delete-orphan” attribute mean? You have more info at:

http://www.nhforge.org/doc/nh/en/index.html#collections-onetomany

The programmer can add and remove chapters from/to a book object, and NHibernate will take care of their persistence. If an object is removed from the list via code, NHibernate will delete the removed chapter IF it has not added to OTHER book. It would be an “orphan” chapter.

This is the code to add and retrieve a book with chapters:

ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory();
using (ISession session = sessionFactory.OpenSession())
{
    using (ITransaction tx = session.BeginTransaction())
    {
        Book cookbook = new Book()
        {
            Title = "NHibernate Cookbook",
            Author = "Jason Dentler",
            Chapters = new List<Chapter>() { 
                new Chapter() { Title = "Models and Mappings" },
                new Chapter() { Title = "Configuration and Schema" },
                new Chapter() { Title = "Sessions and Transactions" }
            }
        };
        session.Save(cookbook);
        foreach (Book book in session.Query<Book>())
        {
            System.Console.WriteLine(string.Format("Book {0}", book.Title));
            int nchapter = 0;
            foreach (Chapter chapter in book.Chapters)
                System.Console.WriteLine(string.Format("Chapter {0}:{1}", ++nchapter, chapter.Title));
        }
        tx.Commit();
        session.Close();
    }
}

I had a problem: initially, I defined ChapterIndex and BookId as not-null in Chapter table. But, notably, NHibernate requires that those fields could accept null values. So, in my first attempts, I got an error when I save a new book. Then, I read the note (from the above link in NHForge documentation):

Very Important Note: If the <key> column of a <one-to-many> association is declared NOT NULL, NHibernate may cause constraint violations when it creates or updates the association. To prevent this problem, you must use a bidirectional association with the many valued end (the set or bag) marked as inverse="true". See the discussion of bidirectional associations later in this chapter.

I changed the columns to nullable, and all worked OK. I’m still puzzle by this requirement: I expected that NHibernate filled the BookId and ChapterIndex DURING the saving of each chapter. But not. It saved the chapter with those fields with null, and THEN, it updated the row.

The code of this post, as usual, is in my AjCodeKatas Google Project, under trunk/NHibernate/BooksOneToMany. You can download a “current frozen” version from NHibernate3BooksOneToMany.zip.

Next steps: explore more one-to-many options, bidirectional mappings, and more.

My sources of information: book: Jason Dentler’s NHibernate 3.0 Cookbook

http://ayende.com/Blog/archive/2006/12/02/nhibernatecascadesthedifferentbetweenallalldeleteorphansandsaveupdate.aspx

http://www.nhforge.org/doc/nh/en/index.html#collections-onetomany

Keep tuned!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

Theme: Shocking Blue Green. Get a free blog at WordPress.com

Follow

Get every new post delivered to your Inbox.

Join 67 other followers