views:

779

answers:

3

The following code throws a StaleStateException exception when the Order.OrderItems property (an IList) is Commited. The full text of the exception is:

An unhandled exception of type 'NHibernate.StaleStateException' occurred in NHibernate.dll

Additional information: Unexpected row count: 0; expected: 1

I've just started using NHibernate, and this means absolutely nothing to me. Can anyone explain what's wrong?

Most of the code is appended below. Sorry it's so much, but I figured that's better than maybe leaving out something important.

If I comment out the line OrderItems = orderItems, everything else works fine.

using System;
using System.Collections.Generic;
using System.IO;
using AutomappingSample.Domain;
using FluentNHibernate.AutoMap;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;

namespace AutomappingSample
{
    class Program
    {
        private const string DbFile = "AutomappingSample.db";
        private const bool useSqlServerCe = true;

        static void Main()
        {
            var factory = CreateSessionFactory();
            using (var session = factory.OpenSession())
            using(var tx = session.BeginTransaction())
            {
                var product1 = new Product
                                   {
                                       Name = "Apples",
                                       UnitPrice = 4.5m,
                                       Discontinued = true
                                   };
                var product2 = new Product
                                   {
                                       Name = "Pears",
                                       UnitPrice = 3.5m,
                                       Discontinued = false
                                   };
                session.Save(product1);
                session.Save(product2);

                var customer = new Customer
                                   {
                                       FirstName = "John",
                                       LastName = "Doe",
                                   };
                session.Save(customer);

                var orderItems = new List<OrderItem>
                                     {
                                         new OrderItem {Id = 1, Quantity = 100, Product = product1},
                                         new OrderItem {Id = 2, Quantity = 200, Product = product2},
                                     };
                var order = new Order()
                                {
                                    Customer = customer, 
                                    Id = 1, 
                                    OrderDate = DateTime.Now, 

                                    // CAUSES FOLLOWING EXCEPTION WHEN COMMIT:
                                    //      An unhandled exception of type 'NHibernate.StaleStateException' 
                                    //      occurred in NHibernate.dll
                                    //
                                    //      Additional information: Unexpected row count: 0; expected: 1
                                    OrderItems = orderItems
                                };
                session.Save(order);

                // EXCEPTION IS THROWN HERE
                tx.Commit();

          }

            Console.WriteLine("Hit enter to exit...");
            Console.ReadLine();
        }

        private static ISessionFactory CreateSessionFactory()
        {
            IPersistenceConfigurer persistenceConfigurer;
            if (useSqlServerCe)
                persistenceConfigurer =
                    MsSqlCeConfiguration.Standard.ConnectionString(c => c.Is("Data Source=AutomappingSample.sdf"));
            else
                persistenceConfigurer = SQLiteConfiguration.Standard.UsingFile(DbFile);

            return Fluently.Configure()
                .Database(persistenceConfigurer)
                .Mappings(m => m.AutoMappings.Add(
                                   AutoPersistenceModel
                                       .MapEntitiesFromAssemblyOf<Customer>()
                                       .WithSetup(s => { s.IsBaseType = type => type == typeof (EntityBase); })
                                       .Where(t => t.Namespace.EndsWith("Domain"))
                                       .ConventionDiscovery.Add<MyStringLengthConvention>()
                                       .ConventionDiscovery.Add<MyIdConvention>()
                                       .ConventionDiscovery.Add<MyForeignKeyConvention>()
                                   ))
                .ExposeConfiguration(BuildSchema)
                .BuildSessionFactory();
        }

        private static void BuildSchema(Configuration config)
        {
            // delete the existing db on each run (only for SQLite)
            if (File.Exists(DbFile))
                File.Delete(DbFile);

            // this NHibernate tool takes a configuration (with mapping info in)
            // and exports a database schema from it
            new SchemaExport(config)
                .Create(true, true);
        }
    }
}


namespace AutomappingSample.Domain
{
    public class EntityBase
    {
        public virtual int Id { get; set; }
    }
}


using System;
using System.Collections.Generic;

namespace AutomappingSample.Domain
{
    public class Order : EntityBase
    {
        public virtual DateTime OrderDate { get; set; }
        public virtual Customer Customer { get; set; }
        public virtual IList<OrderItem> OrderItems { get; set; }
    }
}


namespace AutomappingSample.Domain
{
    public class OrderItem : EntityBase
    {
        public virtual int Quantity { get; set; }
        public virtual Product Product { get; set; }
    }
}
+1  A: 

You may need to tell it to cascade the saves to the OrderItems on Order.

Something like this: (from here)

.Override<Order>(map =>
{
  map.HasMany(x => x.OrderItems)
    .Cascade.All();
});
Derek Ekins
@Derek - Where do I add this code??? This is my first exposure to "fluent" interfaces, and I'm having some difficulty determining where certain methods can be called. Is there a document that lists all the Fluent NHibernate classes and methods in one place?
Tom Bushell
Oops missed that part! It is on the AutoPersistenceModel class.this part:AutoPersistenceModel.MapEntitiesFromAssemblyOf<Customer>()although, I think this is an older syntax, on the version I am using (updated several days ago) it is AutoMap.AssemblyOf<Customer>()So I am not sure if there is some other difference there I am missing.Something else you may like to try, is adding an Order property to OrderItems, FNH is pretty clever and may pick up that link and then add the cascading on automatically.. worth a try anyway.
Derek Ekins
@Derek - You are correct that my example used old syntax. It was the ONLY working example I could find, but turns out it used old versions of Fluent NHibernate. I've tried to convert it to the latest versions with the new syntax, but with no success. Am again getting exceptions, with error messages that are absolute gibberish to me. My impression is that AutoMapping is cool when it works, but extremely difficult to troubleshoot when it does not.
Tom Bushell
Sorry to hear you are having problems. I found it pretty easy to get working just following the example on the page I linked to.If you are having trouble with the generated mappings then you can always write them to disk to see what it is doing. Sometimes the assumptions FNH makes are different to the assumptions we make.To write out the files you can call WriteMappingsTo on the AutoPersistenceModel class.
Derek Ekins
+2  A: 

You need to first save orderItems before attempting to save the order:

session.Save(orderItems[0]);
session.Save(orderItems[1]);
session.Save(order);
Darin Dimitrov
+1 @darin - this worked. Thanks!
Tom Bushell
+1  A: 

Since posting the question, I've learned that the easiest way to get cascading saves is to add a DefaultCascade convention.

See the section in the code below that starts "var autoPersistanceModel = ..."

    private static ISessionFactory CreateSessionFactory()
    {
        ISessionFactory sessionFactory = null;

        // Automapped XML files will be exported to project's
        // ...\bin\x86\Debug\AutoMapExport directory
        // See ".ExportTo()" below
        const string autoMapExportDir = "AutoMapExport";
        if( !Directory.Exists(autoMapExportDir) )
            Directory.CreateDirectory(autoMapExportDir);

        try
        {
            var autoPersistenceModel = 
                AutoMap.AssemblyOf<DlsAppOverlordExportRunData>()
                        // Only map entities in the DlsAppAutomapped namespace
                       .Where(t => t.Namespace == "DlsAppAutomapped")
                       // Do cascading saves on all entities so lists 
                       // will be automatically saved 
                       .Conventions.Add( DefaultCascade.All() )
                ;

            sessionFactory = Fluently.Configure()
                .Database(SQLiteConfiguration.Standard
                              .UsingFile(DbFile)
                              // Display generated SQL on Console
                              .ShowSql()
                         )
                .Mappings(m => m.AutoMappings.Add(autoPersistenceModel)
                                             // Save XML mapping files to this dir
                                             .ExportTo(autoMapExportDir)
                         )
                .ExposeConfiguration(BuildSchema)
                .BuildSessionFactory()
                ;
        }
        catch (Exception e)
        {
            Debug.WriteLine(e);
        }

        return sessionFactory;
    }
Tom Bushell

related questions