gpt4 book ai didi

c# - NHibernate - 没有显式更新的意外更新

转载 作者:行者123 更新时间:2023-11-30 22:47:19 25 4
gpt4 key购买 nike

在对父集合执行 SQL 选择时,其中包含一项的子集合被延迟加载后,随后会为该子集合执行更新语句 - 无需显式调用更新。

父映射:

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

实体时间戳如下所示:

公共(public)类 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());
}

时间戳在事件监听器中设置:

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 选择时,会执行地址实体的更新。

通过使用另一种方法,我已经检查了在自动更新之前传递给事件监听器的地址是否脏了。但是所有的 Prop 似乎都是一样的。

那会是什么?您需要更多信息吗?

我在更新时检查地址是否脏的方法:

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调试:

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

最佳答案

我遇到了几乎相同的问题。我创建了非空字段和允许空值的更新字段。看起来你的两个都为非空,所以你可以简单地设置更新的字段,我在下面设置创建的字段。

我混合使用事件监听器。我不能使用 PreInsert 事件来填充“创建的”字段,因为它发生在处理的后期,并且在 PreInsert 触发之前我得到空检查错误。我使用 PreUpdate 事件是因为我找不到可靠的方法来判断实体是否真的脏了,如果我在 OnSaveOrUpdate 中设置“last_updated”字段,它肯定会使实体变脏并强制每次发布更新。通过使用 PreUpdate,我已经让 NHibernate 检查脏了,我只是在更新触发之前注入(inject)我的值。

查看此 ayende blog有关 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;
}

}

然后确保连接到所需的事件监听器...

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

关于c# - NHibernate - 没有显式更新的意外更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2281924/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com