C# web应用程序场景中的NHibernate乐观并发问题

C# web应用程序场景中的NHibernate乐观并发问题,c#,nhibernate,web-applications,dto,optimistic-concurrency,C#,Nhibernate,Web Applications,Dto,Optimistic Concurrency,我使用版本字段来控制ASP.NET MVC 4应用程序中的并发性 映射: <class name="User" table="Users"> <id name="Id"> <generator class="native"/> </id> <version name="Version" column="Version"/> ... other fields

我使用版本字段来控制ASP.NET MVC 4应用程序中的并发性

映射:

      <class name="User" table="Users">
        <id name="Id">
          <generator class="native"/>
        </id>
        <version name="Version" column="Version"/>
... other fields omitted for brevity...
我采用以下方法:

  • 按Id读取实体并映射到my UserDto实体(Dto用于数据传输对象模式),还包括版本字段
  • 显示用于编辑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);
//使用NHibernate周围的my repository包装从数据库中读取原始实体
var rep=RepositoryFactory.Create(currentUnitOfWork);
User originalEntity=rep.GetById(userDto.Id);
//乐观锁定控制-保持用户看到的版本
originalEntity.Version=userDto.Version;
... 为简洁起见,省略了其他字段。。。
代表更新(原始性);
问题是,即使userDto.Version与originalEntity.Version不匹配,NHibernate也会忽略我的userDto.Version并使用originalEntity.Version(显然,是从一级缓存中读取的,因为实体刚刚被读取)。 这种行为使我的版本字段完全无用

如何强制NHibernate使用我提供的版本值而不是缓存的版本值?

另外,对于使用我的存储库的其他程序员来说,以某种方式使版本控制更加透明也很好,但目前我不知道如何让它自动从接收到的实体中获取版本,并在程序员甚至没有注意到的情况下强制NHibernate使用它


有什么想法吗?

您需要意识到的是,下面示例中的乐观并发只在作为该web请求的一部分创建的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);
正如在本文中详细介绍的,我希望您更愿意称之为与以下内容松散一致的东西

var @object = userDto.ToUser();
myisession.SaveOrUpdate(@object);
你可以看看@

然而,由于拦截器是旧概念,您可以在自定义事件侦听器中扩展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);
    }
}
这里唯一让人头疼的是GetCurrentVersion发送一个DB调用。因此,这不是一个非常适合云应用程序的解决方案,因为每个呼叫都是收费的

或者,您可以从会话中逐出对象。但是,您将失去第一级缓存优势


如果我找到更好的解决方案,我会发布。

我知道这是一个老问题,但我会把我通常的方法留在这里

这是使用ORMs时的一个已知“问题”。NHibernate和Entity Framework都会遇到此“问题”,这是因为ORM在内部跟踪版本值,而不是使用属性返回的版本值。与EF不同,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();
    }
}
public void编辑(int-id、字符串描述、日期时间版本)
{
使用(var session=sessionFactory.OpenSession())
使用(var tx=session.BeginTransaction())
{
var record=session.Get(id);
record.Version=Version;
记录。描述=描述;
session.execute(record);//从会话中逐出对象
session.Update(record);//NHibernate将附加对象,并使用您的版本
tx.Commit();
}
}
如果您像我在模型类()中通常使用的那样使用接口,您可以轻松创建一些扩展方法,使人们更难忘记


据我所知,我没有发现这种方法有任何问题,但是如果你发现了问题,请告诉我。

你确定你的映射文件配置正确吗?它应该生成一个更新语句,如
update People SET…其中PersonID=@p0和Version=@p1
其中
@p1
=由用户origina读取的版本lly.看到了吗?是的,它确实生成了,但是Version的值是它刚从数据库中读取的值,而不是我在originalEntity.Version=userDto.Version中手动设置的值。看来,NHibernate只是忽略了我对Version字段的手动更新。如果我在更新行中断并更改数据库中的版本,那么我会得到ObjectStaleStateException,正如预期的那样。我希望依赖NHib为我做这件事。毕竟,NHibernate抛出一个ObjectStaleStateException,如果我在rep.Update行调试中断时修改数据库中的实体,那么我应该告诉NHib使用我传递的版本……如果NHib开发人员忘记了该版本可能与我从一个分离的对象。哦,感谢文档链接,现在我更好地理解了“11.4.4”。应用程序版本检查“这一章是关于。遗憾的是,NHib不能使用我自己的版本值,即使我显式地设置了它。我想,这可能是一个新的功能要求…@Martin你有没有找到更好的解决方案?这是NHibernate的一个严重缺陷。我还不确定我是否需要手动操作?这肯定是一种常见的情况吗?
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);
    }
}
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();
    }
}