1

Я использую поле Version для управления параллелизмом в приложении ASP.NET MVC 4.Проблемы с оптимистичным параллелизмом NHibernate в сценарии веб-приложения

Mapping:

 <class name="User" table="Users"> 
     <id name="Id"> 
      <generator class="native"/> 
     </id> 
     <version name="Version" column="Version"/> 
... other fields omitted for brevity... 

Entity:

public class User 
    { 
      public virtual int Id { get; set; } 
      public virtual int Version { get; set; } 
... other fields omitted for brevity... 

Я использую следующий подход:

  • чтения примитив Id и карту на мой UserDto лица (Dto для данных перенос объекта), в том числе поле версии
  • показать форма для редактирования UserDto сущность
  • получать публикуемую UserDto сущность

Тогда я следующее:

 // read the original entity from the database using my repository wrapper around NHibernate 
     var rep = RepositoryFactory.Create<User>(currentUnitOfWork); 
     User originalEntity = rep.GetById(userDto.Id); 

     // optimistic lock control - keep the version as the user saw it 
     originalEntity.Version = userDto.Version; 
... other fields omitted for brevity... 

     rep.Update(originalEntity); 

Проблема заключается в том, что даже если userDto.Version не соответствует originalEntity.Version , NHibernate игнорирует мою userDto.Version и использует initialEntity.Version (очевидно, из кеша первого уровня, поскольку объект был просто прочитан). Такое поведение делает поле моей версии совершенно бесполезным.

Как заставить NHibernate использовать предоставленное мной значение Version, а не кешированное?

Кроме того, было бы здорово как-то сделать контроль версий более прозрачным для других программистов, которые используют мой репозиторий, но в настоящее время у меня нет идей, как заставить его автоматически получать версию из полученного объекта и заставлять NHibernate использовать его без программирования, даже замечая его.

Любые идеи?

+0

У вас есть файл настроек, правильно настроенный? он должен генерировать инструкцию обновления, например 'UPDATE People SET ... WHERE PersonID = @ p0 AND Version = @ p1', где' @ p1' = версия, как прочитанная пользователем изначально. см. статью [Ayendes post on NHibernate Concurrency] (http://ayende.com/blog/3946/nhibernate-mapping-concurrency) –

+0

Да, это сгенерирует это, но значение Версии является тем, которое оно только что прочитало из базы данных и а не тот, который я установил вручную в originalEntity.Version = userDto.Version. Кажется, NHibernate просто игнорирует мое обновление вручную в поле «Версия». Если я сломаю строку «Обновление» и изменим версию в базе данных, я получу ObjectStaleStateException, как и ожидалось. – JustAMartin

ответ

2

Что вам нужно понять, так это то, что оптимистичный параллелизм в приведенном ниже примере работает только в рамках ISession, созданного как часть этого веб-запроса. Поэтому, если у пользователя есть значение версии 5 при существовании этого запроса, то это будет то, что будет использоваться для обеспечения того, чтобы строка пользователя не обновлялась.

// read the original entity from the database using my repository wrapper around NHibernate 
    var rep = RepositoryFactory.Create<User>(currentUnitOfWork); 
    User originalEntity = rep.GetById(userDto.Id); 

    // optimistic lock control - keep the version as the user saw it 
    originalEntity.Version = userDto.Version; 
    ... other fields omitted for brevity... 

    rep.Update(originalEntity); 

Поэтому, чтобы получить требуемое поведение, вы должны были бы сделать

User originalEntity = rep.GetById(userDto.Id); 
if (originalEntity.Version != userDto.Version) throw new ConcurrencyException(); 

Как подробно описано в documentation я ожидаю, что вы предпочли бы называть что-то слабо выровненной к следующему

var @object = userDto.ToUser(); 
myisession.SaveOrUpdate(@object); 
+0

Я надеялся полагаться на NHib, делая это для меня. В конце концов, NHibernate бросает ObjectStaleStateException, если я изменяю объект в базе данных при отладке в строке rep.Update, поэтому я должен как-то сказать NHib использовать мою переданную версию ... Странно, если разработчики NHib забыли о возможность того, что версия может исходить от отдельного объекта. – JustAMartin

+0

О, спасибо за ссылку на документацию, теперь я лучше понимаю, о чем говорится в главе «11.4.4. Проверка версии приложения». Жаль, что NHib не может использовать мое собственное значение версии, даже если я его явно установил. Я думаю, это может быть новый запрос функции ... – JustAMartin

+0

@Martin Вы когда-нибудь находили лучшее решение? Это серьезный недостаток в NHibernate. Я все еще не уверен, что мне придется делать это вручную? это общий сценарий? –

0

Вы можете посмотреть @this

Однако, как перехватчики старая концепция, вы можете расширить DefaultUpdateEventListener в качестве слушателя пользовательских событий и переопределить OnSaveOrUpdate метод, как показано ниже

var source = @event.Session; 
var entity = @event.Entity; 
var persister = @event.Session.GetEntityPersister(@event.EntityName, entity); 
if (persister.IsVersioned) 
{ 
    var mode = source.GetSessionImplementation().EntityMode; 
    var id = persister.GetIdentifier(entity, mode); 

    var version = persister.GetVersion(entity, mode); 
    var currentVersion = persister.GetCurrentVersion(id, source); 

    if (!version.Equals(currentVersion)) 
    { 
     throw new StaleObjectStateException(persister.EntityName, id); 
    } 
} 

Единственная головная боль здесь GetCurrntVersion посылает DB вызов. Таким образом, это не очень подходящее решение для облачных приложений, так как каждый вызов выставлен счет.

В качестве альтернативы, вы можете Evict объект с сеанса. Но вы потеряете преимущество кеширования 1-го уровня.

Если я нахожу лучшее решение, опубликую сообщение.

1

Я знаю, что это старый вопрос, но я остану свой обычный подход здесь.

Это проблема с «проблемами» при использовании ORM. «NHibernate» и «Entity Framework» страдают этой «проблемой», и это происходит из-за того, что ORM отслеживает внутреннее значение версии, а не использует значение, возвращенное этим свойством. В отличие от EF, где вы можете копировать значения байта [], используя Array.Copy , в NHibernate я обычно вычеркиваю сущность из сеанса, а затем делаю обновление, которое указывает NHibernate, что вы обновляете существующий объект, но он начнет отслеживать, используя только что назначенную версию.

Вот фрагмент кода:

public void Edit(int id, string description, DateTime version) 
{ 
    using (var session = sessionFactory.OpenSession()) 
    using (var tx = session.BeginTransaction()) 
    { 
     var record = session.Get<Record>(id); 
     record.Version = version; 
     record.Description = description; 

     session.Evict(record); // evict the object from the session 
     session.Update(record); // NHibernate will attach the object, and will use your version 

     tx.Commit(); 
    } 
} 

Если вы используете интерфейс, как я обычно делаю в моих классов (example), вы можете легко создать некоторый метод расширения, что делает его более трудным для людей, чтобы забыть.

Насколько я знаю, я не нашел никаких проблем с этим подходом, но дайте мне знать, если вы обнаружите что-либо.