nhibernatenhibernate-mappingnhibernate-mapping-by-code

Insert into SQL Server database through NHibernate 4 with Session.Save using mapping-by-code


Currently experiencing issues with Session.Save() not inserting records and producing the following exception:

        null id in NHModels.Domain.Activity entry (don't flush the Session after an exception occurs)

at NHibernate.Event.Default.DefaultFlushEntityEventListener.CheckId(Object obj, IEntityPersister persister, Object id, EntityMode entityMode)
at NHibernate.Event.Default.DefaultFlushEntityEventListener.GetValues(Object entity, EntityEntry entry, EntityMode entityMode, Boolean mightBeDirty, ISessionImplementor session)
at NHibernate.Event.Default.DefaultFlushEntityEventListener.OnFlushEntity(FlushEntityEvent event)
at NHibernate.Event.Default.AbstractFlushingEventListener.FlushEntities(FlushEvent event)
at NHibernate.Event.Default.AbstractFlushingEventListener.FlushEverythingToExecutions(FlushEvent event)
at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)
at NHibernate.Impl.SessionImpl.Flush()
at NHibernate.Transaction.AdoTransaction.Commit()
at NHUnitOfWork.Dispose() in NHUnitOfWork.cs:line
at DatabaseActivityOperations.<WriteActivity>d__6.MoveNext() in DatabaseActivityOperations.cs:line 240

My class and mapping look like this.

Activity (For simplicity sake, I've removed several ILists related to this class)

public class Activity {

    public Activity() {
        Activityschema = new List<ActivitySchema>();
    }

    public virtual int ActivityKey { get; set; }
    public virtual string Activityname { get; set; }
    public virtual string Activitydescription { get; set; }
    public virtual DateTime Averageactivitytime { get; set; }
    public virtual int Averagenumberpeople { get; set; }
    public virtual string Worktype { get; set; }
    public virtual bool? Canautocomplete { get; set; }
    public virtual IList<ActivitySchema> Activityschema { get; set; }
}

ActivityMap

public class ActivityMap : ClassMapping<Activity> {

    public ActivityMap() {
        Schema("dbo");
        Lazy(true);
        Id(x => x.ActivityKey, map => { map.Generator(Generators.Identity); });
        Property(x => x.Activityname, map => { map.NotNullable(true); map.Length(50); });
        Property(x => x.Activitydescription, map => { map.NotNullable(true); map.Length(100); });
        Property(x => x.Averageactivitytime, map =>
        {
            map.NotNullable(true);
            map.Type(NHibernateUtil.Time);
        });
        Property(x => x.Averagenumberpeople, map => { map.NotNullable(true); map.Precision(10); });
        Property(x => x.Worktype, map => { map.NotNullable(true); map.Length(50); });
        Property(x => x.Canautocomplete);
        Bag(x => x.Activityschema, colmap =>  { colmap.Key(x => x.Column("ActivityKey")); colmap.Inverse(true); }, map => { map.OneToMany(); }); 
    }
}

Finally, here's the Unit of Work class I have:

public class NHUnitOfWork : IDisposable
{
    public static string ConnectingString { get; private set; } = @"data source=nh;initial catalog=db;MultipleActiveResultSets=True;";
    protected static Configuration _config;
    protected static NHibernate.ISessionFactory _sessionFactory;
    public NHibernate.ISession Session { get; private set; }
    protected NHibernate.ITransaction Transaction { get; set; }
    private const System.Data.IsolationLevel ISOLATION_LEVEL = System.Data.IsolationLevel.ReadUncommitted;
    private bool RollBack { get; set; } = false;

    public NHUnitOfWork(string databaseConnectionString)
    {
        if (_config == null)
        {
            var cfg = new Configuration();
            cfg.DataBaseIntegration(db =>
            {
                db.Driver<NHibernate.Driver.SqlClientDriver>();
                db.ConnectionString = @"data source=nh;initial catalog=db;MultipleActiveResultSets=True;";
                //db.ConnectionString = databaseConnectionString;
                db.Dialect<MsSql2012Dialect>();
                db.BatchSize = 500;
            })
            .AddAssembly(typeof(Activity).Assembly)
            .SessionFactory()
            .GenerateStatistics();

            var mapper = new ModelMapper();
            mapper.AddMappings(typeof(ActivityMap).Assembly.GetTypes());
            cfg.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities());
            _config = cfg;
            _sessionFactory = _config.BuildSessionFactory();
        }
        Session = _sessionFactory.OpenSession();
        Transaction = Session.BeginTransaction(ISOLATION_LEVEL);
        RollBack = false;
    }

    public void Commit()
    {
        Transaction.Commit();
    }

    public void Rollback()
    {
        if (Transaction.IsActive) Transaction.Rollback();
    }

    public void Dispose()
    {
        if (RollBack)
        {
            Transaction.Rollback();
        }
        else
        {
            Transaction.Commit();
        }

        Session.Close();
    }
}

By this point, I believe my configuration for this is correct. I'm successfully used Session.Query to read data and that doesn't present issues. The problem comes when I write something like this to add a new record:

    var activity = new Activity
    {
        Activityname = "TestActivity",
        Activitydescription = "This is a test",
        Averagenumberpeople = 1,
        Worktype = "Test",
        Canautocomplete = false,
        Averageactivitytime = new DateTime(1, 1, 1, 0, 55, 55)
    };
    using (var uow = new NHUnitOfWork(NHUnitOfWork.ConnectingString))
    {
        uow.Session.Save(activity); // Produces exception here

        //This also produces an exception
        //uow.Session.Save(activity, Generators.Identity);
    }

I think this has something to do with how I'm mapping the ID in ActivityMap and the generator isn't working as expected. I've tried to change it to several other types and get the same exception, or one stating that it's not able to convert to SystemInt32. I've also tried changing the ID to long and specifiying a data type, but no luck. What do I seem to be doing wrong here?


Solution

  • Literally minutes after I posted this, I figured out that the issue was actually with how I was setting the date time.

    new DateTime(1, 1, 1, 0, 55, 55)
    

    It didn't like the "1/1/0001" part, so that seemed to be causing the issue, I'm only concerned with the time piece. Changing the year to something like 2001 fixed the insert and it's working fine, the message was just not very descriptive.