2

我正在使用 Version 字段来控制 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...

实体:

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

我正在使用以下方法:

  • 按 Id 读取实体并映射到我的 UserDto 实体(Dto 用于数据传输对象模式),还包括 Version 字段
  • 显示用于编辑 UserDto 实体的表单
  • 接收 POSTed 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 并使用 originalEntity.Version(显然,来自一级缓存,因为刚刚读取了实体)。这种行为使我的版本字段完全无用。

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

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

有任何想法吗?

4

3 回答 3

2

您需要意识到的是,以下示例中的乐观并发仅在作为Web 请求的一部分创建的 ISession 的范围内工作。因此,如果在此请求中 User 的 Version 值为 5,那么它将用于确保 User 行没有被更新。

    // 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();

文档中所述,我希望您更愿意调用与以下内容松散对齐的内容

var @object = userDto.ToUser();
myisession.SaveOrUpdate(@object);
于 2013-04-12T11:07:00.583 回答
1

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

这是使用 ORM 时已知的“问题”。NHibernate 和实体框架都遇到了这个“问题”,这是因为 ORM 在内部跟踪版本值,而不是使用属性返回的值。与 EF 不同,您可以使用 Array.Copy 复制 byte[] 值,在 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();
    }
}

如果您像我通常在模型类(示例)中那样使用接口,您可以轻松创建一些扩展方法,使人们更难忘记。

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

于 2015-09-29T15:42:33.647 回答
0

你可以看看@这个

但是,由于拦截器是旧概念,您可以在自定义事件侦听器中扩展 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 发送一个数据库调用。因此,对于云应用程序来说,这不是一个非常合适的解决方案,因为每次通话都要计费。

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

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

于 2014-02-26T10:47:07.347 回答