Saturday, July 16, 2011

NHibernate concurrency checking on ASP.NET MVC

NHibernate concurrency handling on ASP.NET MVC is pretty boring, no need to introduce any work-around code :-)

Contrast with Entity Framework which need work-around(connected mode simulation) when it is working in disconnected state, NHibernate don't need any of that. NHibernate concurrent update is automatically handled behind the scene, no need to introduce any code to make NHibernate be informed which column(rowversion) is not needed on UPDATE's SET clause, what you just need is to inform NHibernate which property is your rowversion column, and no need to simulate connected mode.


Here's your typical user-friendly(and programmer-friendly if I may add) concurrent update handling with NHibernate:

[HttpPost]
public ActionResult Save(Song song)
{
    using (var s = Mapper.GetSessionFactory().OpenSession())
    using (var tx = s.BeginTransaction())
    {
        try
        {
            s.SaveOrUpdate(song); // SaveOrUpdate automatically fetches the primary key and row version

            tx.Commit();


            ModelState.Remove("SongId");
            // no need to issue ModelState.Remove on Version property; with byte array, ASP.NET MVC always get its value from the model, not from ModelState
            // ModelState.Remove("Version") 

        }
        catch(StaleObjectStateException ex)
        {                       
            s.Evict(song);
            var dbValues = s.Get<Song>(song.SongId);
            var userValues = song;
           


            if (dbValues == null)
            {
                ModelState.AddModelError("", "This record you are attempting to save is already deleted by other user");
                return;
            }

           
            if (dbValues.SongName != userValues.SongName)
                ModelState.AddModelError("SongName", "Current value made by other user: " + dbValues.SongName);

            
            if (dbValues.AlbumName != userValues.AlbumName)
                ModelState.AddModelError("AlbumName", "Current value made by other user: " + dbValues.AlbumName);

            ModelState.AddModelError("", 
                "The record you attempted to edit was already modified by another user since you last loaded it. Open the latest changes on this record");

        }
        catch (Exception ex)
        {
            ModelState.AddModelError("", ex.Message);
        }


        return View("Input", song);
    }
}

Get the DDL at http://www.ienablemuch.com/2011/07/entity-framework-concurrency-checking.html

Complete demo code: http://code.google.com/p/nh-concurrency-handling-on-aspnet-mvc-demo/downloads/list

Sample output:

Entity Framework concurrency checking on ASP.NET MVC (UpdateModel is the only sane approach when you want to use concurrency checking on EF)

UPDATE August 2, 2011

Perhaps I'm too harsh on Entity Framework, we can simplify concurrency checking if we used Timestamp attribute. ConcurrencyCheck doesn't work well on detached(e.g. web) scenario, work-arounds is needed. On the updated code, we change ConcurrencyCheck attribute to Timestamp attribute

Though for complex object persisting (e.g. many-to-many. http://www.ienablemuch.com/2011/07/using-checkbox-list-on-aspnet-mvc-with_16.html), even we used Timestamp attribute, we still need OriginalValue work-around. Directly persisting a detached entity which has children entities would result to duplicate children entities, as EF has no knowledge that the old children entities should be discarded; so for this type of data persistence we need to to re-simulate attached scenario, that means we directly work on entities that come from EF, and that means we need to inform EF the entity's original rowversion value if we want EF to handle concurrent updates well. To prevent duplicate children entities on an entity, we need to clear the attached entity's children; on our checkbox list demo, that is movie.Genres.Clear(). Don't worry, clearing the children entities is efficient, it doesn't actually delete the children rows on database, try to check the Sql Server profiler :-)

public class Song
{
    [Key]
    public int SongId { get; set; }
    public string SongName { get; set; }
    public string AlbumName { get; set; }

    [Timestamp]
    public virtual byte[] Version { get; set; }
}


Here's the updated Save method:

[HttpPost]
public ActionResult Save(Song song)
{
    SaveCommon(song);

    return View("Input", song); // song is the model
}



private void SaveCommon(Song song)
{
    using (var db = new TheMusicContext())
    {
        try
        {
            if (song.SongId == 0)
                db.Songs.Add(song);                                                
            else
                db.Entry(song).State = System.Data.EntityState.Modified;

            db.SaveChanges();

            ModelState.Remove("SongId"); // to force ASP.NET MVC to get the SongId value from the model, not from ModelState
        }
        catch (DbUpdateConcurrencyException ex)
        {
            var entry = ex.Entries.Single();


            var dbValues = entry.GetDatabaseValues();

            // already deleted
            if ( dbValues == null)
            {
                ModelState.AddModelError("", "This record you are attempting to save is already deleted by other user");
                return;
            }

            
            Song songDbValues = (Song) dbValues.ToObject();
            Song userValues = (Song) entry.Entity;



            if (songDbValues.SongName != userValues.SongName)
                ModelState.AddModelError("SongName", "Current value made by other user: " + songDbValues.SongName);

            if (songDbValues.AlbumName != userValues.AlbumName)
                ModelState.AddModelError("AlbumName", "Current value made by other user: " + songDbValues.AlbumName);

            ModelState.AddModelError("", "The record you attempted to edit was already modified by another user since you last loaded it. Open the latest changes on this record");
        }
        catch (Exception ex)
        {
            ModelState.AddModelError("", ex.Message + "\n" + ex.StackTrace + ex.InnerException.Message);
        }
        
      
    }

               
}//SaveCommon

Here's the updated Delete method:

public ActionResult Delete(int id, byte[] Version)
{
    using (var db = new TheMusicContext())
    {

        var stub = new Song { SongId = id, Version = Version };
        if (Request.HttpMethod == "POST")
        {
            try
            {                        
                db.Entry(stub).State = System.Data.EntityState.Deleted;
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            catch (DbUpdateConcurrencyException ex)
            {
                var entry = ex.Entries.Single();
                var dbValues = entry.GetDatabaseValues();


                // already deleted
                if (dbValues == null)
                    return RedirectToAction("Index");
                                 
                ModelState.AddModelError("", "The record you are attempting to delete was modified by other user first. Do you still want to delete this record?");
                Song songDbValues = (Song)dbValues.ToObject();       

                return View(songDbValues);
            }                    
        }
        else
        {
            var songToDelete = db.Songs.Find(id);
            return View(songToDelete);
        }
    }
}


Get the updated code here: http://code.google.com/p/ef-concurrency-on-aspnet-mvc-demo/downloads/list




**STALE post. read the recommended procedure above**

Entity Framework concurrency handling doesn't work out-of-the-box if you are using it in disconnected scenario(as typified by web apps). Contrast to this(connected): http://www.ienablemuch.com/2011/05/entity-frameworks-concurrency-handling.html

When you submitted(POST) a form, you must simulate your entity like it was just first loaded from the GET method(e.g. http://Song/Edit/1), essentially you need to simulate connected mode so EF can compare if the record's rowversion have changed since it was first loaded. On the code below, that simulation is in line #22 and 23


ASP.NET's MVC model binding doesn't seem to be of much use on Entity Framework. Entity Framework cannot work with the posted model directly. Contrast that with NHibernate http://www.ienablemuch.com/2011/07/nhibernate-concurrency-checking-on.html

[HttpPost]
public ActionResult Save(Song song)
{
    using (var db = new TheMusicContext())
    {
        try
        {
            bool isNew = song.SongId == 0;

            var songToDb = !isNew ? db.Songs.Find(song.SongId) : song;

            if (songToDb == null) throw new Exception("This record you are attempting to save is already deleted by other user");


            if (isNew)
                db.Songs.Add(songToDb); // Primary key is automatically populated
            else
            {
                // To facilitate concurrency checks, do these two lines.
                // When EF perform an update, it compares the DB value from OriginalValue.
                // Leaky abstractions rear its ugly head when you want to do concurrency checks on EF ;-)
                db.Entry(songToDb).Property("Version").OriginalValue = song.Version;
                db.Entry(songToDb).State = System.Data.EntityState.Unchanged;

                // There's too much implicitness here, UpdateModel get values from Request.Form/Request.QueryString, Model Binding (variable song) isn't of much use here.
                // But UpdateModel is the only sane approach when you want to use concurrency checking on EF,
                // as it has array parameter, to which you can selectively pass properties via Linq-to-objects
                // Automapper is another approach? http://automapper.codeplex.com/
                // Is Model Binding useless on EF?
                // One thing for sure, Model Binding is not useless on NHibernate :-)
                // http://www.ienablemuch.com/2011/07/nhibernate-concurrency-checking-on.html
                UpdateModel(songToDb, 
                      typeof(Song).GetProperties()
                      .Where(x => x.Name != "Version")
                      .Select(x => x.Name).ToArray());
            }


            db.SaveChanges();

            db.Entry(songToDb).Reload(); // Fetch the Version column from the database
            song.Version = songToDb.Version;


            ModelState.Remove("SongId"); // to force ASP.NET to get the SongId value from the model, not from ModelState

            // no need to issue ModelState.Remove on Version property; with byte array, ASP.NET MVC always get its value from the model, not from ModelState
            // ModelState.Remove("Version") 
        }
        catch (DbUpdateConcurrencyException ex)
        {
            var entry = ex.Entries.Single();
            Song dbValues = (Song)entry.GetDatabaseValues().ToObject();
            Song userValues = (Song)entry.Entity;



            if (dbValues.SongName != userValues.SongName)
                ModelState.AddModelError("SongName", "Current value made by other user: " + dbValues.SongName);

            if (dbValues.AlbumName != userValues.AlbumName)
                ModelState.AddModelError("AlbumName", "Current value made by other user: " + dbValues.AlbumName);

            ModelState.AddModelError("", "The record you attempted to edit was already modified by another user since you last loaded it. Open the latest changes on this record");
        }
        catch (Exception ex)
        {
            ModelState.AddModelError("", ex.Message);
        }
        

        return View("Input", song); // song is the model

    }       
}


Model:
public class Song
{
    [Key]
    public int SongId { get; set; }
    public string SongName { get; set; }
    public string AlbumName { get; set; }

    [ConcurrencyCheck]
    public virtual byte[] Version { get; set; }
}

The View:
@model EfConcurrencyOnAspNetMvc.Models.Song

@{
    ViewBag.Title = "Input";
}

<h2>Input</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@{ string controllerName = (string) ViewContext.RouteData.Values["Controller"]; }

@using (Html.BeginForm("Save", controllerName)) {
    @Html.ValidationSummary(true)

    @Html.HiddenFor(x => x.SongId)
    @Html.HiddenFor(x => x.Version)
    <fieldset>
        <legend>Song</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.SongName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.SongName)
            @Html.ValidationMessageFor(model => model.SongName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.AlbumName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.AlbumName)
            @Html.ValidationMessageFor(model => model.AlbumName)
        </div>

        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

The DDL:
create table Song
(
SongId int identity(1,1) not null primary key,
SongName varchar(50) not null,
AlbumName varchar(50) not null,
Version rowversion not null
);

Complete demo code: http://code.google.com/p/ef-concurrency-on-aspnet-mvc-demo/downloads/list


Sample Output:

Monday, July 11, 2011

NHibernate object-relational mapping discipline

What will happen to your language-embedded query(e.g. Linq) techniques if you are not yet weaned off on mapping table fields to objects by its physical implementation, i.e. you are using public virtual int ProductId { get; set; }
instead of using public virtual Product Product { get; set; }


That is, instead of this logical mapping, see line #18:

public class SalesHeader
{
 public virtual int SalesHeaderId { get; set; }
 public virtual string CustomerName { get; set; }

 public virtual string OrNum { get; set; }
 public virtual DateTime OrDate { get; set; }

 public virtual IList<SalesDetail> Sales { get; set; }      
}


public class SalesDetail 
{
 public virtual SalesHeader SalesHeader { get; set; }

 public virtual int SalesDetailId { get; set; }
 public virtual Product Product { get; set; }
 public virtual int Qty { get; set; }
 public virtual decimal UnitPrice { get; set; }
 public virtual decimal Amount { get; set; }            
}

You just do physical mapping, note line# 6:

public class SalesDetail 
{
 public virtual SalesHeader SalesHeader { get; set; }

 public virtual int SalesDetailId { get; set; }
 public virtual int ProductId { get; set; }
 public virtual int Qty { get; set; }
 public virtual decimal UnitPrice { get; set; }
 public virtual decimal Amount { get; set; }            
}

If you want to fashion your query to something like this (find all SalesHeader which has a Product that starts with M):
select *
from SalesHeader h
where exists(
 select * 
 from SalesDetail d 
 join Product p on p.ProductId = d.ProductId 
 where d.SalesHeaderId = h.SalesHeaderId
  and p.Product.Name like 'M%'
 )

This is how your Linq will look like if you map your database to objects physically:
var x =
 from h in s.Query<SalesHeader>()
 where h.Sales.Any(d =>
  s.Query<Product>().Where(p => p.ProductId == d.ProductId && p.ProductName.StartsWith("M")).Any())
 select h;



that looks convoluted, and the resulting query(columns removed for brevity) is even more:

exec sp_executesql 
N'select * 
from [SalesHeader] salesheade0_ 
where exists (
 select * from [SalesDetail] sales1_ 
 where salesheade0_.SalesHeaderId=sales1_.SalesHeaderId and 
  (exists (select *  from [Product] product2_ 
    where product2_.ProductId=sales1_.ProductId and (product2_.ProductName like (@p0+''%'')))))',N'@p0 nvarchar(4000)',@p0=N'M'


The resulting query, though achieves the same output with our desired query, is a far cry from simple query, it results to a bit complex query, it doesn't link SalesDetail to Product, it uses another EXISTS.


Another attempt, one-to-one with our desired query:

var x = from h in s.Query<SalesHeader>()
 where
 (
  (from d in s.Query<SalesDetail>()
  join p in s.Query<Product>() on d.ProductId equals p.ProductId
  where d.SalesHeader.SalesHeaderId == h.SalesHeaderId
   && p.ProductName.StartsWith("M")
  select d).Any()
 )
 select h;


And that generates this:

exec sp_executesql 
N'select *
from [SalesHeader] salesheade0_ 
where exists (
 select *
 from [SalesDetail] salesdetai1_, [Product] product2_ 
 where product2_.ProductId=salesdetai1_.ProductId and salesdetai1_.SalesHeaderId=salesheade0_.SalesHeaderId 
  and (product2_.ProductName like (@p0+''%'')))',N'@p0 nvarchar(4000)',@p0=N'M'  

Now it look the same with our desired query, though Linq-wise, it can be argued that it doesn't achieve so much in terms of readability and productivity.



To make Linq matters simple, use proper modelling with your ORM

public class SalesDetail 
{
 public virtual SalesHeader SalesHeader { get; set; }

 public virtual int SalesDetailId { get; set; }
 public virtual Product Product { get; set; }
 public virtual int Qty { get; set; }
 public virtual decimal UnitPrice { get; set; }
 public virtual decimal Amount { get; set; }            
}


The Linq will be now this simple, and it achieves the same desired query. The difference is minor, it use table comma table instead of join.

var x = from h in s.Query<SalesHeader>()
  where h.Sales.Any(d => d.Product.ProductName.StartsWith("M"))
  select h;


The generated query:

exec sp_executesql 
N'select *
from [SalesHeader] salesheade0_ 
where exists (
 select sales1_.SalesDetailId 
 from [SalesDetail] sales1_, [Product] product2_ 
 where salesheade0_.SalesHeaderId=sales1_.SalesHeaderId 
  and sales1_.ProductId=product2_.ProductId and (product2_.ProductName like (@p0+''%'')))',N'@p0 nvarchar(4000)',@p0=N'M'
  

Now the Linq is more maintainable and intuitive, and follows our desired optimized query :-)

Form_Load exception troubleshooting

Don't put code in Form_Load event if possible, silent errors will creep in when you are running inside Visual Studio. Error will manifest only when the EXE is run directly

private void Form1_Load(object sender, EventArgs e)
{
    int n = 0;
    MessageBox.Show((7 / n).ToString()); // won't throw an Exception when run inside VS
}

private void Form1_Shown(object sender, EventArgs e)
{
    int n = 0;
    MessageBox.Show((7 / n).ToString()); // will throw an exception, regardless of where it is run
}

Sunday, July 10, 2011

Using checkbox list on ASP.NET MVC with NHibernate

The core logic of checkbox list:

@foreach (var g in Model.GenreSelections)
{                                
    <input type="checkbox" name="SelectedGenres" value="@g.GenreId" @(Model.SelectedGenres.Contains(g.GenreId) ? "checked" : "") /> @g.GenreName
}

Add the models

Movie model

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;


namespace AspNetMvcCheckboxList.Models
{
    public class Movie
    {
        public virtual int MovieId { get; set; }
        
        [   Required, Display(Name="Title")
        ]   public virtual string MovieName { get; set; }
        
        [   Required, Display(Name="Description")
        ]   public virtual string MovieDescription { get; set; }
        
        [   Required, Display(Name="Year Released"), Range(1900,9999)
        ]   public virtual int? YearReleased { get; set; }
        
        public virtual byte[] Version { get; set; }

        
        public virtual IList<Genre> Genres { get; set; }               
    }
}


Genre model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace AspNetMvcCheckboxList.Models
{
    public class Genre
    {
        public virtual int GenreId { get; set; }
        public virtual string GenreName { get; set; }

        public virtual IList<Movie> Movies { get; set; }
    }
}


Then add a ViewsModels folder on your project, and add the following view model, note line #15 and #17:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using AspNetMvcCheckboxList.Models;

namespace AspNetMvcCheckboxList.ViewsModels
{
    public class MovieInputViewModel
    {
        public string MessageToUser { get; set; }

        public Movie TheMovie { get; set; }
        
        public List<Genre> GenreSelections{ get; set; }

        public List<int> SelectedGenres { get; set; }
    }
}

Add the Controller

Add the Movie controller, and add this Input action, note line #10:
public ViewResult Input(int id = 0)
{
    using (var s = Mapper.GetSessionFactory().OpenSession())
    {
        var movie = id != 0 ? s.Get<Movie>(id) : new Movie { Genres = new List<Genre>() };
        return View(new MovieInputViewModel
        {
            TheMovie = movie,
            GenreSelections = s.Query<Genre>().OrderBy(x => x.GenreName).ToList(),
            SelectedGenres = movie.Genres.Select(x => x.GenreId).ToList()
        });
    }
}

and add the Save action below. Note line #11, if the user don't check anything, the SelectedGenres won't be populated by ASP.NET MVC, and will be left as null. Note line #16, that pushes the selected genres by a user to Movie's Genre collection, use Load, don't use Get. When saving the third table, we are not interested on getting the genre record from database, only its ID(which is already known by the application, database round-trip is not necessary and will not happen), Load prevents eager fetching of object from database. Load doesn't hit the database, it just prepare the object's proxy by assigning it an ID; which when it's time for the program to access the object's properties other than the object ID, that's the time the object will hit the database(to fetch the rest of the properties of the object) by means of its ID(primary key).

[HttpPost]
public ActionResult Save(MovieInputViewModel input)
{
    using (var s = Mapper.GetSessionFactory().OpenSession())
    using (var tx = s.BeginTransaction())
    {
        try
        {
            bool isNew = input.TheMovie.MovieId == 0;

            input.SelectedGenres = input.SelectedGenres ?? new List<int>();
            input.GenreSelections = s.Query<Genre>().OrderBy(x => x.GenreName).ToList();

            input.TheMovie.Genres = new List<Genre>();
            foreach (int g in input.SelectedGenres)
                input.TheMovie.Genres.Add(s.Load<Genre>(g));


            s.SaveOrUpdate(input.TheMovie); // Primary key(MovieId) is automatically set with SaveOrUpdate, and the row version (Version) field too.


            tx.Commit();


            ModelState.Remove("TheMovie.MovieId");

            // No need to remove TheMovie.Version, ASP.NET MVC is not preserving the ModelState of variables with byte array type.
            // Hence, with byte array, the HiddenFor will always gets its value from the model, not from the ModelState
            // ModelState.Remove("TheMovie.Version"); 


            input.MessageToUser = string.Format("Saved. {0}", isNew ? "ID is " + input.TheMovie.MovieId : "");

        }
        catch (StaleObjectStateException)
        {
            ModelState.AddModelError("",
                "The record you attempted to edit was already modified by another user since you last loaded it. Open the latest changes on this record");
        }
                       
    }
    
    return View("Input", input);
}

Note on Save code last line, we just re-use the Input's view.


Add the supporting View

This is our Input's view, note line #56, that's the checkbox list mechanism; we also take into account the concurrent update of same movie, line #22:

@model AspNetMvcCheckboxList.ViewsModels.MovieInputViewModel

@{
    ViewBag.Title = "Movie";
}


<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>


@{ string controllerName = (string) ViewContext.RouteData.Values["Controller"]; }

@using (Html.BeginForm("Save", controllerName))
{
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>Movie</legend>

        
        @Html.HiddenFor(model => model.TheMovie.MovieId)
        @Html.HiddenFor(model => model.TheMovie.Version)

        <div class="editor-label">
            @Html.LabelFor(model => model.TheMovie.MovieName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.TheMovie.MovieName)
            @Html.ValidationMessageFor(model => model.TheMovie.MovieName)
        </div>


        <div class="editor-label">
            @Html.LabelFor(model => model.TheMovie.MovieDescription)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.TheMovie.MovieDescription)
            @Html.ValidationMessageFor(model => model.TheMovie.MovieDescription)
        </div>


        <div class="editor-label">
            @Html.LabelFor(model => model.TheMovie.YearReleased)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.TheMovie.YearReleased)
            @Html.ValidationMessageFor(model => model.TheMovie.YearReleased)
        </div>


        <fieldset>
            <legend>Genres</legend>
            
            @foreach (var g in Model.GenreSelections)
            {                                
                <input type="checkbox" name="SelectedGenres" value="@g.GenreId" @(Model.SelectedGenres.Contains(g.GenreId) ? "checked" : "") /> @g.GenreName                
            }
        </fieldset>

        <p>
            <input type="submit" value="Save" />

            
            <a href="@Url.Content(string.Format(@"~/{0}/Input", controllerName))">New</a>

            
        </p>
    </fieldset>
    
    <p>@Model.MessageToUser</p>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>


Add the mapper

Uses automapping via Fluent NHibernate. The third table(MovieAssocGenre) is in line 38,39.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

using NHibernate;
using NHibernate.Dialect;

using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Automapping;
using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.Instances;
using FluentNHibernate.Conventions.Helpers;

using AspNetMvcCheckboxList.Models;


namespace AspNetMvcCheckboxList
{
    public static class Mapper
    {
        static ISessionFactory _sf = null;
        public static ISessionFactory GetSessionFactory()
        {
            if (_sf != null) return _sf;

            var fc = Fluently.Configure()
                    .Database(MsSqlConfiguration.MsSql2008.ConnectionString(@"Data Source=localhost;Initial Catalog=TestNhCheckboxList;User id=sa;Password=P@$$w0rd"))
                    .Mappings
                    (m =>
                            m.AutoMappings.Add
                            (
                                AutoMap.AssemblyOf<MvcApplication>(new CustomConfiguration())
                                   .Conventions.Add(ForeignKey.EndsWith("Id"))
                                   .Conventions.Add<RowversionConvention>()  
                                   .Override<Movie>(x => x.HasManyToMany(y => y.Genres).Table("MovieAssocGenre") /* .ParentKeyColumn("MovieId").ChildKeyColumn("GenreId") // optional, the conventions overrides take care of these :-) */ )
                                   .Override<Genre>(x => x.HasManyToMany(y => y.Movies).Table("MovieAssocGenre") /* .ParentKeyColumn("GenreId").ChildKeyColumn("MovieId") // optional, the conventions overrides take care of these :-) */ )
                            )
                    // .ExportTo(@"C:\_Misc\NH")                
                    );


            // Console.WriteLine( "{0}", string.Join( ";\n", fc.BuildConfiguration().GenerateSchemaCreationScript(new MsSql2008Dialect() ) ) );
            // Console.ReadLine();

            _sf = fc.BuildSessionFactory();
            return _sf;
        }


        class CustomConfiguration : DefaultAutomappingConfiguration
        {
            IList<Type> _objectsToMap = new List<Type>()
            {
                // whitelisted objects to map
                typeof(Movie), typeof(Genre)
            };
            public override bool ShouldMap(Type type) { return _objectsToMap.Any(x => x == type); }
            public override bool IsId(FluentNHibernate.Member member) { return member.Name == member.DeclaringType.Name + "Id"; }            
        }


        class RowversionConvention : IVersionConvention
        {
            public void Apply(IVersionInstance instance) { instance.Generated.Always(); }
        }


    }
}

And this is the physical implementation. The R in ORM :-)

create table Movie
(
MovieId int identity(1,1) not null primary key,
MovieName varchar(100) not null unique,
MovieDescription varchar(100) not null,
YearReleased int not null,
Version rowversion not null
);



create table Genre
(
GenreId int identity(1,1) not null primary key,
GenreName varchar(100) not null unique
);


create table MovieAssocGenre
(
MovieAssocGenreId int identity(1,1) not null primary key,
MovieId int not null references Movie(MovieId),
GenreId int not null references Genre(GenreId),
constraint uk_MovieAssocGenre unique(MovieId, GenreId)
);



insert into Genre(GenreName) values('Action'),('Comedy'),('Documentary'),('Romance'),('Sci-Fi'),('Thriller');


Get the code here: http://code.google.com/p/aspnet-mvc-checkbox-list-demo/downloads/list

To contrast NHibernate approach to Entity Framework, visit http://www.ienablemuch.com/2011/07/using-checkbox-list-on-aspnet-mvc-with_16.html

Sample output: