Mar 30, 2010

AdoNetBatchSize и StatelessSession

Для выполнения bulk операций над базой данных в NHibernate есть специальная StatelessSession. Интерфейс данного объекта отличается от стандартного Session, у него нет методов Save, SaveOrUpdate и т.д., зато есть Insert, Update, Delete.

Особенность StatelessSession в том, что она не следит за сохранёнными объектами, значит это следующее, рассмотрим следующий пример кода:

using(var transaction = Session.BeginTransaction())
{
    var someObject = Session.Get<SomeObject>(id);
    someObject.SomeProperty = "some new value";
    transaction.Commit();
}

В результате выполнения этого кода someObject будет обновлен не только в вашем приложении, но в и базе данных.

За подобное приходится платить производительностью, в частности именно это стало причиной того, что на OrmBattle Nhibernate получает далеко не самые лучшие отметки (кстати по этому поводу есть довольно хороший ответ от Ayende).

А теперь перейдем к основной теме. Показать хотелось бы следующее:

ISessionFactory sessionFactory = Fluently.Configure()
                                         .Database(MsSqlConfiguration.MsSql2008.ConnectionString("connString")
                                         .AdoNetBatchSize(40))  // important!
                                         .Mappings(x=>x.FluentMappings.AddFromAssemblyOf<TestHilo>())
                                         .BuildSessionFactory();

IStatelessSession session = sessionFactory.OpenStatelessSession();

for (int i = 0; i < 60; i++)
{
    var testHilo = new TestHilo
    {
        Field1 = "field" + i, 
        Field2 = i
    };

    session.Insert(testHilo);
}

session.Dispose();

Данный код должен вставить 60 записей в базу. На самом же деле вставится только 40. Происходит это из-за установленного AdoNetBatchSize (подробнее об этом в предыдущем посте). Когда это обнаружилось первое что захотелось сделать это вызвать session.Flush(). Но у StatelessSession нет такого метода :).

В общем решением будет следующее – выполнять данный цикл в рамках одной транзацкии:

using (ITransaction transaction = session.BeginTransaction())
{
    for (int i = 0; i < 60; i++)
    {
        var testHilo = new TestHilo
                                {
                                    Field1 = "field" + i,
                                    Field2 = i
                                };

        session.Insert(testHilo);
    }

    transaction.Commit();
}

Такой код вставит все 60 записей.