2010-02-17 45 views
2

在子項集合中有一個項目在其父項中執行SQL選擇時獲得延遲加載後,會爲此子項執行更新語句 - 而不顯式調用更新。NHibernate - 沒有明確更新的意外更新

父映射:

<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> 

實現:

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; 
    } 

} 

兒童映射:

<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> 

兒童實施:

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; 
    } 
} 

的entitiy時間戳的樣子:

公共類EntityTimestamp:IValueObject { 私人的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()); 
} 

}

時間戳設置了事件偵聽器內:

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); 
     } 

所以執行父設備上的SQL選擇時,則執行地址實體的更新。

通過使用另一種方法,我已經檢查過地址是否被自動更新之前傳遞給事件偵聽器,如果它是髒的。但所有的道具似乎都是一樣的。

這可能是什麼?你需要更多信息?

我檢查,如果該地址已被弄髒更新的方法:

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); 
} 

NHibernate的SQL調試:

//父實體選擇

NHibernate.SQL :2010-02-17 16:18:39,357 [21] DEBUG NHibernate.SQL [(null) ] - 。 SELECT * FROM( SELECT SPR *, SPFT [等級], ROW_NUMBER()OVER(ORDER BY SPFT [等級] DESC)AS ROWNUM FROM CONTAINSTABLE(ParentEntitySpecialTable, 計算機, ''some text'') AS spft INNER JOIN ParentEntity spr ON spr.ParentEntityId = spft。[鍵]

   ) 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:39513 [21] DEBUG NHibernate.SQL [(空)] - 選擇 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 爲CityId 11_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_,CIT y1_.ZipCode如 ZipCode9_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_,一個ddresstyp2_.ChangedOn 爲ChangedOn6_1_, addresstyp2_.ChangedBy作爲 ChangedBy6_1_發件人地址addresses0_ 內加入市city1_上 addresses0_.CityId = city1_.CityId內 上 addresses0_.AddressTypeId = addresstyp2_.AddressTypeId WHERE(addresses0_加入地址類型addresstyp2_。請將isDeleted = 0)和 [email protected]; @ P0 = 345625 'ASPNET_WP.EXE'(管理): 加載 'CountryProxyAssembly' 'ASPNET_WP.EXE'(管理):加載 'CountryProxyModule'

//地址i š更新

NHibernate.SQL:

2010-02-17 16:18:51607 [21] DEBUG NHibernate的。SQL [(null)] - 批處理 命令:命令0:UPDATE地址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

//地址被更新

NHibernate.SQL:

2010-02-17 16:19:03748 [21] DEBUG NHibernate.SQL [(空)] - 批量 命令:命令0:更新地址SET 街= @ P0,StreetNumber = @ P1, PostOfficeBox = @ P2,請將isDeleted = @ P3, CityId = @ P4,AddressTypeId = @ P5, ValidityPeriodFrom = @ P6, ValidityPeriodTo = @ p7,Crea tedOn = @ 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 = 2010年2月17日16時19分03秒, @ P11 = 'FFF',@ P12 = 117390

+0

如果你註釋掉的部分,你更新的時間戳在SaveEventListener中。我懷疑是造成這個問題......如果是這樣,我會發布我如何解決這個問題。 – dotjoe 2010-02-17 16:18:32

+0

是的,你是對的。如果從地址中刪除時間戳記,或者如果我評論設置在事件偵聽器中的時間戳記,則不會執行意外更新。 – Chris 2010-02-17 16:40:18

回答

3

我碰到的幾乎是同樣的問題。我創建了非null的字段,並更新了允許空值的字段。它看起來像你有兩個非空,所以你可以簡單地設置更新的字段,我在下面設置創建的字段。

我使用事件偵聽器的混合。我無法使用PreInsert事件來填充「已創建」字段,因爲它發生在處理的晚期,並且在PreInsert觸發之前我會得到空的檢查錯誤。我使用PreUpdate事件,因爲我找不到一個可靠的方法來判斷實體是否真的髒,如果我在OnSaveOrUpdate中設置了「last_updated」字段,它肯定會使實體變髒,並強制每次發佈更新。通過使用PreUpdate,我讓NHibernate檢查髒兮兮的事情,並且在更新激發之前我簡單地注入我的值。

請參閱本ayende blog關於更新前

更多信息
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; 
    } 

} 

,然後一定要掛鉤到所需的事件監聽器......

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