NHibernate的 - 没有明确的更新意外的更新 [英] NHibernate - Unexpected update without explicit update

查看:106
本文介绍了NHibernate的 - 没有明确的更新意外的更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

执行后对父的SQL选择时,单个项目的子集在它变得懒加载,更新语句的执行这个孩子以后 - 无需显式调用更新。

父映射:

 <休眠映射的xmlns =金塔:NHibernate的映射 -  2.2
    命名空间=ParentEntity
    装配=ParentEntity>

  <类名=ParentEntity表=ParentEntity>

    < ID名称=ID列=ParentEntityId未保存值= -  1>
      <生成器类=身份/>
    < / ID>

    <包名=地址访问=字段逆=真级联=全删除,孤儿,其中=请将isDeleted = 0>
      <键列=ParentEntityId/>
      &其中;一对许多类=地址/>
    < /袋>

  < /类>

< /休眠映射>
 

执行:

 公共类ParentEntity:IEntity< ParentEntity>中IAuditableEntity,IDeletableEntity
{

    私人的ICollection<地址>地址;


    保护ParentEntity()
    {
        地址=新名单,其中;地址>();

    }



    公共虚拟的ICollection<地址>地址
    {
        得到
        {
            返回新的List<地址>(addresses.Where(A =>!a.IsDeleted和放大器;&安培;!a.Validity.IsExpired))。AsReadOnly();
        }
        私定
        {
            地址=价值;
        }
    }

    公共虚拟的ICollection<地址> ExpiredAddresses
    {
        得到
        {
            返回新的List<地址>(addresses.Where(A =>!a.IsDeleted和放大器;&安培; a.Validity.IsExpired))。AsReadOnly();
        }
    }



    #地区IAuditableEntity会员

    公共虚拟EntityTimestamp时间戳
    {
        {返回时间戳; }
        集合{时间戳=价值; }
    }

    #endregion


    公共虚拟BOOL AddAddress(地址地址)
    {
        如果(addresses.Contains(地址)|| ExpiredAddresses.Contains(地址))
            返回false;

        address.ParentEntity =这一点;

        addresses.Add(地址);

        返回true;
    }

    公共虚拟BOOL RemoveAddress(地址地址)
    {
        如果(!addresses.Contains(地址)及和放大器;!ExpiredAddresses.Contains(地址))
            返回false;

        address.IsDeleted = TRUE;
        返回true;
    }

}
 

儿童映射:

 <休眠映射的xmlns =金塔:NHibernate的映射 -  2.2
    命名空间=...
    装配=...>

  <类名=地址表=地址>

    < ID名称=ID列=AddressId未保存值= -  1>
      <生成器类=身份/>
    < / ID>

    <属性名=街>< /性>
    <属性名=StreetNumber>< /性>
    <属性名=PostOfficeBox>< /性>
    <属性名=请将isDeleted不是空=真正的>< /性>

    <多到一个名称=市不空=真正的列=CityId懒惰=假级联=无提取=加入级=城与GT;< /多-to一>

    <多到一个名称=类型不是空=真正的列=AddressTypeId懒惰=假级联=无提取=加入级=地址类型>< /多-to一>

    <多到一个名称=ParentEntity不是空=真正的更新=假列=ParentEntityId懒惰=假级联=无提取=加入级=ParentEntity&GT ;&所述; /多到1>


    <成分名称=时间戳级=EntityTimestamp>
      <属性名=CreatedOn不是空=真/>
      <成分名称=CreatedBy级=用户>
        <属性名=姓名不是空=真正的列=CreatedBy/>
      < /成分>
      <属性名=ChangedOn不是空=真/>
      <成分名称=ChangedBy级=用户>
        <属性名=姓名不是空=真正的列=ChangedBy/>
      < /成分>
    < /成分>

  < /类>

< /休眠映射>
 

儿童实施:

 公共类地址:IEntity<地址&gt ;, IAuditableEntity,IDeletableEntity
{
    // ID等..

    私人EntityTimestamp时间戳;
    私人城市的城市;
    私人请将isDeleted布尔;
    私人字符串街;
    私人字符串postOfficeBox;
    私人字符串streetNumber;
    私人有效性有效性;
    私人地址类型类型;
    私人ParentEntity parentEntity;

    公共虚拟EntityTimestamp时间戳
    {
        {返回时间戳; }
        集合{时间戳=价值; }
    }

    公共虚拟请将isDeleted布尔
    {
        {返回请将isDeleted; }
        集合{=请将isDeleted价值; }
    }

    公共虚拟字符串街
    {
        {返回街道; }
        集合{街道=价值; }
    }

    公共虚拟字符串StreetNumber
    {
        {返回streetNumber; }
        集合{streetNumber =价值; }
    }

    公共虚拟字符串PostOfficeBox
    {
        {返回postOfficeBox; }
        集合{postOfficeBox =价值; }
    }

    公共虚拟城市城市
    {
        {返回城市; }
        集合{城市=价值; }
    }

    公共虚拟地址类型类型
    {
        {返回类型; }
        集合{类型=值; }
    }

    公共虚拟有效性有效性
    {
        {返回有效性; }
        集合{有效性=价值; }
    }

    受保护的内部虚拟ParentEntity ParentEntity
    {
        {返回parentEntity; }
        集合{parentEntity =价值; }
    }

    受保护的地址()
    {
    }

    公共广播(有效性有效性)
    {
        this.validity =有效性;
    }
}
 

的entitiy时间戳是这样的:

公共类EntityTimestamp:IValueObject {     私营的DateTime createdOn;

  public虚拟的DateTime CreatedOn
{
    {返回createdOn; }
    私人集合{createdOn =价值; }
}

私人IUSER createdBy;


公共虚拟IUSER CreatedBy
{
    {返回createdBy; }
    私人集合{createdBy =价值; }
}

私营的DateTime changedOn;


公共虚拟的DateTime ChangedOn
{
    {返回changedOn; }
    私人集合{changedOn =价值; }
}

私人IUSER changedBy;


公共虚拟IUSER ChangedBy
{
    {返回changedBy; }
    私人集合{changedBy =价值; }
}


保护EntityTimestamp()
{
}

私人EntityTimestamp(日期时间createdOn,IUSER createdBy,日期时间changedOn,IUSER changedBy)
{
    如果(createdBy == NULL)
        抛出新的ArgumentException(创建用户为空);

    如果(changedBy == NULL)
        抛出新的ArgumentException(由用户更改为空);

    this.createdOn = createdOn;
    this.createdBy = createdBy;
    this.changedBy = changedBy;
    this.changedOn = changedOn;
}

公共静态EntityTimestamp新()
{
    返回新EntityTimestamp(新的DateTime precise()现在,SecurityService.Current.GetCurrentUser(),新的日期时间precise()现在,SecurityService.Current.GetCurrentUser());
}

公共静态EntityTimestamp新(IUSER forUser)
{
    返回新EntityTimestamp(新的DateTime precise()现在,forUser,新的日期时间precise()现在,forUser。);
}

公共静态EntityTimestamp NewUpdated(IUSER forUser,EntityTimestamp oldTimestamp)
{
    返回新EntityTimestamp(oldTimestamp.CreatedOn,oldTimestamp.CreatedBy,新的日期时间precise()现在,forUser。);
}

公共静态EntityTimestamp NewUpdated(EntityTimestamp oldTimestamp)
{
    返回新EntityTimestamp(oldTimestamp.CreatedOn,oldTimestamp.CreatedBy,新的日期时间precise()现在,SecurityService.Current.GetCurrentUser());
}
 

}

该时间戳设置一个事件监听器中:

 公共类EntitySaveEventListener:NHibernate.Event.Default.DefaultSaveEventListener
    {
        保护覆盖对象PerformSaveOrUpdate(SaveOrUpdateEvent E)
        {
            如果(e.Entity是IAuditableEntity)
            {
                VAR实体= e.Entity为IAuditableEntity;
                // TODO:CascadeBeforeSave();
                如果(实体!= NULL)
                {
                        IsDirtyEntity(e.Session,e.Entity);
                    如果(entity.IsNew)
                    {
                        entity.Timestamp = EntityTimestamp.New();
                    }
                    其他
                    {
                        entity.Timestamp = EntityTimestamp.NewUpdated(entity.Timestamp);


                    }

                }
            }

            返回base.PerformSaveOrUpdate(E);
        }
 

所以执行父设备上的SQL选择时,则执行该地址的实体的更新。

通过使用另一种方法,我已经如果地址,被传递给事件侦听器之前它被自动更新,如果是脏检查。但是,所有的道具似乎是相同的。

那会是什么?你需要更多的信息?

我查了一下,如果地址是脏上更新的方法:

 公共静态布尔IsDirtyEntity(ISession的会议,对象实体)
{
    字符串的className = NHibernateProxyHelper.GuessClass(实体).FullName;
    ISessionImplementor sessionImpl = session.GetSessionImplementation();
    IPersistenceContext persistenceContext = sessionImpl.PersistenceContext;
    IEntityPersister持留= sessionImpl.Factory.GetEntityPersister(类名);
    EntityEntry oldEntry = sessionImpl.PersistenceContext.GetEntry(实体);


    如果((oldEntry == NULL)及及(实体是INHibernateProxy))
    {
        INHibernateProxy代理=实体INHibernateProxy;
        obj对象= sessionImpl.PersistenceContext.Unproxy(代理);
        oldEntry = sessionImpl.PersistenceContext.GetEntry(OBJ);
    }

    [对象] oldState = oldEntry.LoadedState;
    [对象] currentState的= persister.GetPropertyValues​​(实体,sessionImpl.EntityMode);
    的Int32 [] dirtyProps = persister.FindDirty(currentState的,oldState,实​​体sessionImpl);

    返回(dirtyProps!= NULL);
}
 

NHibernate的SQL调试:

  

//父实体选择

     

NHibernate.SQL:2010-02-17   16:18:39357 [21] DEBUG NHibernate.SQL   [(空值)] -                   选择                       *                     从 (                     选择                         SPR *,                         SPFT [排名]                         ROW_NUMBER()OVER(ORDER BY SPFT。[等级] DESC)AS ROWNUM                     从                         CONTAINSTABLE(ParentEntitySpecialTable,   计算',一些文本)   AS SPFT                         INNER JOIN ParentEntity SPR ON spr.ParentEntityId   = SPFT。[关键]

 )AS结果
                哪里
                  ROWNUM BETWEEN(@ P0  -  1)* @ P1 + 1和@ P2 * @ P3
              ORDER BY
             [等级] DESC; @ P0 = 1,P1 @ = 20,@ p2的= 1,@ P3 = 20
 

     

NHibernate.SQL:2010-02-17   16:18:39513 [21] DEBUG NHibernate.SQL   [(NULL)] - 选择   addresses0_.ParentEntityId作为   ServiceP8_3_,addresses0_.AddressId作为   AddressId3_,addresses0_.AddressId作为   AddressId11_2_,addresses0_.Street作为   Street11_2_,addresses0_.StreetNumber   作为StreetNu3_11_2_,   addresses0_.PostOfficeBox作为   PostOffi4_11_2_,addresses0_.IsDeleted   作为IsDeleted11_2_,addresses0_.CityId   作为CityId11_2_,   addresses0_.AddressTypeId作为   AddressT7_11_2_,   addresses0_.ParentEntityId作为   ServiceP8_11_2_,   addresses0_.ValidityPeriodFrom作为   Validity9_11_2_,   addresses0_.ValidityPeriodTo作为   Validit10_11_2_,addresses0_.CreatedOn   作为CreatedOn11_2_,   addresses0_.CreatedBy作为   CreatedBy11_2_,addresses0_.ChangedOn   作为ChangedOn11_2_,   addresses0_.ChangedBy作为   ChangedBy11_2_,city1_.CityId作为   CityId9_0_,city1_.IsDeleted作为   IsDeleted9_0_,city1_.Name作为   Name9_0_,city1_.Zip code作为   邮编code9_0_,city1_.CountryId作为   CountryId9_0_,city1_.CreatedOn作为   CreatedOn9_0_,city1_.CreatedBy作为   CreatedBy9_0_,city1_.ChangedOn作为   ChangedOn9_0_,city1_.ChangedBy作为   ChangedBy9_0_,   addresstyp2_.AddressTypeId作为   AddressT1_6_1_,addresstyp2_.IsDeleted   作为IsDeleted6_1_,   addresstyp2_.IsSystemDefault作为   IsSystem3_6_1_,addresstyp2_.Name作为   Name6_1_,addresstyp2 _。[重点]作为   column5_6_1_,addresstyp2_.CreatedOn   作为CreatedOn6_1_,   addresstyp2_.CreatedBy作为   CreatedBy6_1_,addresstyp2_.ChangedOn   作为ChangedOn6_1_,   addresstyp2_.ChangedBy作为   ChangedBy6_1_发件人地址addresses0_   内部联接市city1_上   addresses0_.CityId = city1_.CityId内   加入地址类型addresstyp2_上   addresses0_.AddressTypeId = addresstyp2_.AddressTypeId   WHERE(addresses0_.IsDeleted = 0)和   addresses0_.ParentEntityId=@p0; @ P0 =   345625'ASPNET_WP.EXE(管理):   加载CountryProxyAssembly   ASPNET_WP.EXE(管理):加载   CountryProxyModule

     

//地址更新

NHibernate.SQL:

  

2010-02-17 16:18:51607 [21] DEBUG   NHibernate.SQL [(空)] - 批量   命令:命令0:更新地址设置   街道= @ P0,StreetNumber = @ P1,   PostOfficeBox = @ P2,请将isDeleted = @ P3,   CityId = @ P4,AddressTypeId = @ P5,   ValidityPeriodFrom = @ P6,   ValidityPeriodTo = @ P7,CreatedOn =   @ P8,CreatedBy = @ P9,ChangedOn =   @ P10,ChangedBy = @ P11 WHERE AddressId   = @ P12; @ P0 =FFF,@ P1 ='',@ P2 = NULL,@ P3 =假,@ P4 =   116644,@ P5 = 1,@ P6 = 2010年1月20日   十七点28分15秒,@ P7 = 31.12.9999 00:00:00,   @ P8 = 2010年1月20日17时29分52秒,@ P9 =   FFF,@ P10 = 2010年2月17日十六点18分51秒,   @ P11 =FFF,@ P12 = 117390

     

//地址更新

NHibernate.SQL:

  

2010-02-17 16:19:03748 [21] DEBUG   NHibernate.SQL [(空)] - 批量   命令:命令0:更新地址设置   街道= @ P0,StreetNumber = @ P1,   PostOfficeBox = @ P2,请将isDeleted = @ P3,   CityId = @ P4,AddressTypeId = @ P5,   ValidityPeriodFrom = @ P6,   ValidityPeriodTo = @ P7,CreatedOn =   @ P8,CreatedBy = @ P9,ChangedOn =   @ P10,ChangedBy = @ P11 WHERE AddressId   = @ P12; @ P0 =FFF,@ P1 ='',@ P2 = NULL,@ P3 =假,@ P4 =   116644,@ P5 = 1,@ P6 = 2010年1月20日   十七点28分15秒,@ P7 = 31.12.9999 00:00:00,   @ P8 = 2010年1月20日17时29分52秒,@ P9 =   FFF,@ P10 = 2010年2月17日十六点19分03秒,   @ P11 =FFF,@ P12 = 117390

解决方案

我碰到的pretty的许多同样的问题。我已经创建了非空的和更新的领域,允许空值的字段。它看起来就像你有两个非空,所以你可以简单地设置更新的领域,其中我设置下创建的字段。

我用事件侦听器的混合物。我不能用preInsert事件填充创造领域,因为它发生在后期加工和我得到空检查错误之前,preInsert永远闪光。我用preUpdate事件,因为我无法找到一个可靠的方法来判断实体其实是肮脏的,如果我设置OnSaveOrUpdate的last_updated领域,这肯定会使实体脏,强制更新将发行每次。通过使用preUpdate,我已经让NHibernate的检查肮脏和更新闪光之前对的,我只是注入我的价值观。

请参阅此<一个href="http://ayende.com/Blog/archive/2009/04/29/nhibernate-i$p$pupdateeventlistener-amp-i$p$pinserteventlistener.aspx"相对=nofollow> ayende博客上preUpdate更多信息

 公共类AuditableEventListener:DefaultSaveOrUpdateEventListener,我preUpdateEventListener
{
    公众覆盖无效OnSaveOrUpdate(SaveOrUpdateEvent @event)
    {
        可审计的一个= @ event.Entity为可审核;
        若(a!= NULL)
        {
            如果(this.GetEntityState(@ event.Entity,@ event.EntityName,@ event.Entry,@ event.Session)== EntityState.Transient)
            {
                a.create_dt = DateTime.Now;
                a.create_by = @ event.Session.Load&LT;员工及GT;(CurrentStaff.Id);
            }
        }

        base.OnSaveOrUpdate(@event);
    }

    #地区我preUpdateEventListener会员

    公共BOOL在preUpdate(preUpdateEvent @event)
    {
        VAR审计= @ event.Entity为可审核;
        如果(审计== NULL)返回false;

        变种现在= DateTime.Now;
        VAR用户= @ event.Session.Load&LT;员工及GT;(CurrentStaff.Id);

        //非常重要的是保持国家和实体同步在一起
        集(@ event.Persister,@ event.State,last_update_dt,现在的);
        集(@ event.Persister,@ event.State,last_update_by,用户);

        audit.last_update_dt =现在;
        audit.last_update_by =用户;

        返回false;
    }

    #endregion


    私人无效集(IEntityPersister持留,对象[]状态,串propertyName的,目标价值)
    {
        VAR指数= Array.IndexOf(persister.PropertyNames,propertyName的);
        如果(指数== -1)
            返回;
        国家[指数] =值;
    }

}
 

,然后一定要挂钩到所需EventListeners的...

  ISaveOrUpdateEventListener [] saveUpdateListeners =新ISaveOrUpdateEventListener [] {新AuditableEventListener()};
conf.EventListeners.SaveEventListeners = saveUpdateListeners;
conf.EventListeners.SaveOrUpdateEventListeners = saveUpdateListeners;
conf.EventListeners.UpdateEventListeners = saveUpdateListeners;

。conf.EventListeners preUpdateEventListeners =新的I preUpdateEventListener [] {新AuditableEventListener()};
 

after a child collection with one item in it gets lazy loaded when performing an SQL select on the parent, an update statement is executed for this child afterwards - without explicitly calling update.

Parent mapping:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    namespace="ParentEntity" 
    assembly="ParentEntity">

  <class name="ParentEntity" table="ParentEntity">

    <id name="Id" column="ParentEntityId" unsaved-value="-1">
      <generator class="identity"/>
    </id>

    <bag name="addresses" access="field" inverse="true" cascade="all-delete-orphan" where="IsDeleted = 0">
      <key column="ParentEntityId"/>
      <one-to-many class="Address"/>
    </bag>

  </class>

</hibernate-mapping>

Implementation:

public class ParentEntity : IEntity<ParentEntity>, IAuditableEntity, IDeletableEntity
{

    private ICollection<Address> addresses;


    protected ParentEntity()
    {
        addresses = new List<Address>();

    }



    public virtual ICollection<Address> Addresses
    {
        get
        {
            return new List<Address>(addresses.Where(a => !a.IsDeleted && !a.Validity.IsExpired)).AsReadOnly();
        }
        private set
        {
            addresses = value;
        }
    }

    public virtual ICollection<Address> ExpiredAddresses
    {
        get
        {
            return new List<Address>(addresses.Where(a => !a.IsDeleted && a.Validity.IsExpired)).AsReadOnly();
        }
    }



    #region IAuditableEntity Members

    public virtual EntityTimestamp Timestamp
    {
        get { return timestamp; }
        set { timestamp = value; }
    }

    #endregion


    public virtual bool AddAddress(Address address)
    {
        if (addresses.Contains(address) || ExpiredAddresses.Contains(address) )
            return false;

        address.ParentEntity = this;

        addresses.Add(address);

        return true;
    }

    public virtual bool RemoveAddress(Address address)
    {
        if (!addresses.Contains(address) && !ExpiredAddresses.Contains(address))
            return false;

        address.IsDeleted = true;
        return true;
    }

}

Child mapping:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    namespace="..." 
    assembly="...">

  <class name="Address" table="Address">

    <id name="Id" column="AddressId" unsaved-value="-1">
      <generator class="identity"/>
    </id>

    <property name="Street" ></property>
    <property name="StreetNumber" ></property>
    <property name="PostOfficeBox" ></property>
    <property name="IsDeleted" not-null="true" ></property>    

    <many-to-one name="City" not-null="true" column="CityId" lazy="false" cascade="none" fetch="join" class="City"></many-to-one>

    <many-to-one name="Type" not-null="true" column="AddressTypeId" lazy="false" cascade="none" fetch="join" class="AddressType"></many-to-one>

    <many-to-one name="ParentEntity" not-null="true" update="false" column="ParentEntityId" lazy="false" cascade="none" fetch="join" class="ParentEntity"></many-to-one>


    <component name="Timestamp" class="EntityTimestamp">
      <property name="CreatedOn" not-null="true" />
      <component name="CreatedBy" class="User">
        <property name="Name" not-null="true" column="CreatedBy" />
      </component>
      <property name="ChangedOn" not-null="true" />
      <component name="ChangedBy" class="User">
        <property name="Name" not-null="true" column="ChangedBy" />
      </component>
    </component>

  </class>

</hibernate-mapping>

Child implementation:

public class Address : IEntity<Address>, IAuditableEntity, IDeletableEntity
{
    // id etc...

    private EntityTimestamp timestamp;
    private City city;
    private bool isDeleted;
    private string street;
    private string postOfficeBox;
    private string streetNumber;
    private Validity validity;
    private AddressType type;
    private ParentEntity parentEntity;

    public virtual EntityTimestamp Timestamp
    {
        get { return timestamp; }
        set { timestamp = value; }
    }

    public virtual bool IsDeleted
    {
        get { return isDeleted; }
        set { isDeleted = value; }
    }

    public virtual string Street
    {
        get { return street; }
        set { street = value; }
    }

    public virtual string StreetNumber
    {
        get { return streetNumber; }
        set { streetNumber = value; }
    }

    public virtual string PostOfficeBox
    {
        get { return postOfficeBox; }
        set { postOfficeBox = value; }
    }

    public virtual City City
    {
        get { return city; }
        set { city = value; }
    }

    public virtual AddressType Type
    {
        get { return type; }
        set { type = value; }
    }

    public virtual Validity Validity
    {
        get { return validity; }
        set { validity = value; }
    }

    protected internal virtual ParentEntity ParentEntity
    {
        get { return parentEntity; }
        set { parentEntity = value; }
    }

    protected Address()
    {
    }

    public Address(Validity validity)
    {
        this.validity = validity;
    }
}

The entitiy timestamp looks like:

public class EntityTimestamp : IValueObject { private DateTime createdOn;

public virtual DateTime CreatedOn
{
    get { return createdOn; }
    private set { createdOn = value; }
}

private IUser createdBy;


public virtual IUser CreatedBy
{
    get { return createdBy; }
    private set { createdBy = value; }
}

private DateTime changedOn;


public virtual DateTime ChangedOn
{
    get { return changedOn; }
    private set { changedOn = value; }
}

private IUser changedBy;


public virtual IUser ChangedBy
{
    get { return changedBy; }
    private set { changedBy = value; }
}


protected EntityTimestamp()
{
}

private EntityTimestamp(DateTime createdOn, IUser createdBy, DateTime changedOn, IUser changedBy)
{
    if (createdBy == null)
        throw new ArgumentException("Created by user is null.");

    if (changedBy == null)
        throw new ArgumentException("Changed by user is null.");

    this.createdOn = createdOn;
    this.createdBy = createdBy;
    this.changedBy = changedBy;
    this.changedOn = changedOn;
}

public static EntityTimestamp New()
{            
    return new EntityTimestamp(new DateTimePrecise().Now, SecurityService.Current.GetCurrentUser(), new DateTimePrecise().Now, SecurityService.Current.GetCurrentUser());
}

public static EntityTimestamp New(IUser forUser)
{
    return new EntityTimestamp(new DateTimePrecise().Now, forUser, new DateTimePrecise().Now, forUser);
}

public static EntityTimestamp NewUpdated(IUser forUser, EntityTimestamp oldTimestamp)
{
    return new EntityTimestamp(oldTimestamp.CreatedOn, oldTimestamp.CreatedBy, new DateTimePrecise().Now, forUser);
}

public static EntityTimestamp NewUpdated(EntityTimestamp oldTimestamp)
{
    return new EntityTimestamp(oldTimestamp.CreatedOn, oldTimestamp.CreatedBy, new DateTimePrecise().Now, SecurityService.Current.GetCurrentUser());
}

}

The timestamp is set within an event listener:

public class EntitySaveEventListener : NHibernate.Event.Default.DefaultSaveEventListener
    {
        protected override object PerformSaveOrUpdate(SaveOrUpdateEvent e)
        {
            if (e.Entity is IAuditableEntity)
            {
                var entity = e.Entity as IAuditableEntity;
                //todo: CascadeBeforeSave();
                if (entity != null)
                {             
                        IsDirtyEntity(e.Session, e.Entity);       
                    if (entity.IsNew)
                    {
                        entity.Timestamp = EntityTimestamp.New();
                    }
                    else
                    {
                        entity.Timestamp = EntityTimestamp.NewUpdated(entity.Timestamp);


                    }

                }
            }

            return base.PerformSaveOrUpdate(e);
        }

So when performing a SQL select on the parent, an update of the address entity is executed.

By using another method, I already checked if the address, being passed to the event listener before it is updated automatically, if it is dirty. But all props seem to be the same.

What could that be? Do you need more information?

The method I checked if the address is dirty on update:

public static Boolean IsDirtyEntity(ISession session, Object entity)
{
    String className = NHibernateProxyHelper.GuessClass(entity).FullName;
    ISessionImplementor sessionImpl = session.GetSessionImplementation();
    IPersistenceContext persistenceContext = sessionImpl.PersistenceContext;
    IEntityPersister persister = sessionImpl.Factory.GetEntityPersister(className);
    EntityEntry oldEntry = sessionImpl.PersistenceContext.GetEntry(entity);


    if ((oldEntry == null) && (entity is INHibernateProxy))
    {
        INHibernateProxy proxy = entity as INHibernateProxy;
        Object obj = sessionImpl.PersistenceContext.Unproxy(proxy);
        oldEntry = sessionImpl.PersistenceContext.GetEntry(obj);
    }

    Object [] oldState = oldEntry.LoadedState;
    Object [] currentState = persister.GetPropertyValues(entity, sessionImpl.EntityMode);
    Int32 [] dirtyProps = persister.FindDirty(currentState, oldState, entity, sessionImpl);

    return (dirtyProps != null);
}

The nhibernate sql debug:

// parent entity select

NHibernate.SQL: 2010-02-17 16:18:39,357 [21] DEBUG NHibernate.SQL [(null)] - SELECT * FROM ( SELECT spr.*, spft.[Rank], ROW_NUMBER() OVER (ORDER BY spft.[Rank] DESC) AS RowNum FROM CONTAINSTABLE(ParentEntitySpecialTable, Computed, '"some text"') AS spft INNER JOIN ParentEntity spr ON spr.ParentEntityId = spft.[Key]

              ) AS Results 
                WHERE 
                  RowNum BETWEEN (@p0 - 1) * @p1 + 1 AND @p2 * @p3
              ORDER BY 
             [Rank] DESC;@p0 = 1, @p1 = 20, @p2 = 1, @p3 = 20

NHibernate.SQL: 2010-02-17 16:18:39,513 [21] DEBUG NHibernate.SQL [(null)] - SELECT addresses0_.ParentEntityId as ServiceP8_3_, addresses0_.AddressId as AddressId3_, addresses0_.AddressId as AddressId11_2_, addresses0_.Street as Street11_2_, addresses0_.StreetNumber as StreetNu3_11_2_, addresses0_.PostOfficeBox as PostOffi4_11_2_, addresses0_.IsDeleted as IsDeleted11_2_, addresses0_.CityId as CityId11_2_, addresses0_.AddressTypeId as AddressT7_11_2_, addresses0_.ParentEntityId as ServiceP8_11_2_, addresses0_.ValidityPeriodFrom as Validity9_11_2_, addresses0_.ValidityPeriodTo as Validit10_11_2_, addresses0_.CreatedOn as CreatedOn11_2_, addresses0_.CreatedBy as CreatedBy11_2_, addresses0_.ChangedOn as ChangedOn11_2_, addresses0_.ChangedBy as ChangedBy11_2_, city1_.CityId as CityId9_0_, city1_.IsDeleted as IsDeleted9_0_, city1_.Name as Name9_0_, city1_.ZipCode as ZipCode9_0_, city1_.CountryId as CountryId9_0_, city1_.CreatedOn as CreatedOn9_0_, city1_.CreatedBy as CreatedBy9_0_, city1_.ChangedOn as ChangedOn9_0_, city1_.ChangedBy as ChangedBy9_0_, addresstyp2_.AddressTypeId as AddressT1_6_1_, addresstyp2_.IsDeleted as IsDeleted6_1_, addresstyp2_.IsSystemDefault as IsSystem3_6_1_, addresstyp2_.Name as Name6_1_, addresstyp2_.[Key] as column5_6_1_, addresstyp2_.CreatedOn as CreatedOn6_1_, addresstyp2_.CreatedBy as CreatedBy6_1_, addresstyp2_.ChangedOn as ChangedOn6_1_, addresstyp2_.ChangedBy as ChangedBy6_1_ FROM Address addresses0_ inner join City city1_ on addresses0_.CityId=city1_.CityId inner join AddressType addresstyp2_ on addresses0_.AddressTypeId=addresstyp2_.AddressTypeId WHERE (addresses0_.IsDeleted = 0) and addresses0_.ParentEntityId=@p0;@p0 = 345625 'aspnet_wp.exe' (Managed): Loaded 'CountryProxyAssembly' 'aspnet_wp.exe' (Managed): Loaded 'CountryProxyModule'

// address is updated

NHibernate.SQL:

2010-02-17 16:18:51,607 [21] DEBUG NHibernate.SQL [(null)] - Batch commands: command 0:UPDATE Address SET Street = @p0, StreetNumber = @p1, PostOfficeBox = @p2, IsDeleted = @p3, CityId = @p4, AddressTypeId = @p5, ValidityPeriodFrom = @p6, ValidityPeriodTo = @p7, CreatedOn = @p8, CreatedBy = @p9, ChangedOn = @p10, ChangedBy = @p11 WHERE AddressId = @p12;@p0 = 'fff', @p1 = ' ', @p2 = NULL, @p3 = False, @p4 = 116644, @p5 = 1, @p6 = 20.01.2010 17:28:15, @p7 = 31.12.9999 00:00:00, @p8 = 20.01.2010 17:29:52, @p9 = 'fff', @p10 = 17.02.2010 16:18:51, @p11 = 'fff', @p12 = 117390

// address is updated

NHibernate.SQL:

2010-02-17 16:19:03,748 [21] DEBUG NHibernate.SQL [(null)] - Batch commands: command 0:UPDATE Address SET Street = @p0, StreetNumber = @p1, PostOfficeBox = @p2, IsDeleted = @p3, CityId = @p4, AddressTypeId = @p5, ValidityPeriodFrom = @p6, ValidityPeriodTo = @p7, CreatedOn = @p8, CreatedBy = @p9, ChangedOn = @p10, ChangedBy = @p11 WHERE AddressId = @p12;@p0 = 'fff', @p1 = ' ', @p2 = NULL, @p3 = False, @p4 = 116644, @p5 = 1, @p6 = 20.01.2010 17:28:15, @p7 = 31.12.9999 00:00:00, @p8 = 20.01.2010 17:29:52, @p9 = 'fff', @p10 = 17.02.2010 16:19:03, @p11 = 'fff', @p12 = 117390

解决方案

I've run into pretty much the same problem. I have created fields that are non null and updated fields that allow nulls. It looks like you have both as non null so you could simply set the updated fields where I'm setting the created fields below.

I use a mixture of event listeners. I cannot use PreInsert event to populate the "created" fields since it occurs to late in processing and I get null check errors before PreInsert ever fires. I use PreUpdate event because I couldn't find a reliable way to tell if the entity is actually dirty and if I set the "last_updated" fields in OnSaveOrUpdate, it would definitely make the entity dirty and force an update to be issued every time. By using PreUpdate, I've let NHibernate check the dirtiness and I simply inject my values right before the update fires.

See this ayende blog for more info on PreUpdate

public class AuditableEventListener : DefaultSaveOrUpdateEventListener, IPreUpdateEventListener
{
    public override void OnSaveOrUpdate(SaveOrUpdateEvent @event)
    {
        Auditable a = @event.Entity as Auditable;
        if (a != null)
        {
            if (this.GetEntityState(@event.Entity, @event.EntityName, @event.Entry, @event.Session) == EntityState.Transient)
            {
                a.create_dt = DateTime.Now;
                a.create_by = @event.Session.Load<Staff>(CurrentStaff.Id);
            }
        }

        base.OnSaveOrUpdate(@event);
    }

    #region IPreUpdateEventListener Members

    public bool OnPreUpdate(PreUpdateEvent @event)
    {
        var audit = @event.Entity as Auditable;
        if (audit == null) return false;

        var now = DateTime.Now;
        var user = @event.Session.Load<Staff>(CurrentStaff.Id);

        //Very important to keep the State and Entity synced together
        Set(@event.Persister, @event.State, "last_update_dt", now);
        Set(@event.Persister, @event.State, "last_update_by", user);

        audit.last_update_dt = now;
        audit.last_update_by = user;

        return false;
    }

    #endregion


    private void Set(IEntityPersister persister, object[] state, string propertyName, object value)
    {
        var index = Array.IndexOf(persister.PropertyNames, propertyName);
        if (index == -1)
            return;
        state[index] = value;
    }

}

and then be sure to hook up to required eventlisteners...

ISaveOrUpdateEventListener[] saveUpdateListeners = new ISaveOrUpdateEventListener[] { new AuditableEventListener() };
conf.EventListeners.SaveEventListeners = saveUpdateListeners;
conf.EventListeners.SaveOrUpdateEventListeners = saveUpdateListeners;
conf.EventListeners.UpdateEventListeners = saveUpdateListeners;

conf.EventListeners.PreUpdateEventListeners = new IPreUpdateEventListener[] { new AuditableEventListener() };

这篇关于NHibernate的 - 没有明确的更新意外的更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆