使用NHibernate与可追溯性级联



我已经阅读了inversecascade映射属性,想知道:

  • 是否可以在我的场景中使用它们如果是
  • 如何相应地参数化它们

假设我有两个类,CustomerInvoice,它们都需要可追溯性TraceableEntity

我对所有实体都使用Repository模式,因此在那里为存储库注入了一个NHibernate.ISession构造函数。实际上,我为每个实体CustomerInvoice都有一个存储库。

因为我需要用户登录,我认为这与业务模型无关,所以我在存储库Save方法中设置了它,因为只有ISession知道用于连接底层数据库的用户,而存储库依赖于它。这样,业务模型就不会被无用的信息污染。

此外,由于这种可追溯性的需要,我失去了inversecascade映射属性的强大性和易用性,否则,我不知道如何将它们用于我的特定需求。

让我们看看BaseRepository.Save()方法。

public abstract class BaseRepository<T> where T : TraceableEntity {
public BaseRepository(ISession session) { Session = session; }
public ISession Session { get; private set; }
public T Save(T instance) {
if (instance.IsNew && instance.IsDirty) 
instance.Creator = readLoginFromConnectionString();
else if (!instance.IsNew && (instance.IsDirty || instance.IsDeleted))
instance.Updater = readLoginFromConnectionString();
Session.SaveOrUpdate(instance);
return instance;
}
}

TraceableEntity

public abstract class TraceableEntity {
public TraceableEntity() { 
Created = DateTime.Today; 
IsNew = true; 
}
public virtual DateTime Created { get; set; }
public virtual string Creator { get; set; }
public virtual DateTime? Deleted { get; set; }
public virtual int Id { get; protected set; }
public virtual bool IsDeleted { get; set; }
public virtual bool IsDirty { get; set; }
public virtual bool IsNew { get; set; }
public virtual DateTime? Updated { get; set; }
public virtual string Updater { get; set; }
}

Customer

public class Customer : TraceableEntity {
public Customer() : base() { Invoices = new List<Invoice>(); }
public virtual Name { get; set; }
public virtual Number { get; set; }
public virtual IList<Invoice> Invoices { get; private set; }
}

Customer.hbm.xml

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"    
namespace="MyProject.Model" 
assembly="MyProject">
<class name="Customer" table="CUSTOMERS">
<id name="Id" column="CUST_ID" type="Int32" unsaved-value="0">
<generator class="sequence-identity">
<param name="sequence">CUST_ID_SEQ</param>
</generator>
</id>
<property name="Name" column="CUST_NAME" type="String" length="128" not-null="true" />
<property name="Number" column="CUST_NUMBER" type="String" length="12" not-null="true" />
<property name="Creator" column="CUST_CREATOR_USR_ID" type="String" length="15" not-null="true" />
<property name="Created" column="CUST_CREATED_DT" type="DateTime" not-null="true" />
<property name="Updater" column="CUST_UPDATER_USR_ID" type="String" length="15" />
<property name="Updated" column="CUST_UPDATED_DT" type="DateTime" not-null="false" />
<property name="Deleted" column="CUST_DELETED_DT" type="DateTime" not-null="false" />
<bag name="Invoices" table="INVOICES" fetch="join" lazy="true" inverse="true">
<key column="CUST_ID" foreign-key="INV_CUST_ID_FK" />
<one-to-many class="Invoice" />
</bag>
</class>
</hibernate-mapping>

Invoice

public class Invoice : TraceableEntity {
public Invoice() : base() { }
public virtual Customer Customer { get; set; }
public virtual DateTime InvoiceDate { get; set; }
public virtual string Number { get; set; }
public virtual float Total { get; set; }
}

Invoice.hbm.xml

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"    
namespace="MyProject.Model" 
assembly="MyProject">
<class name="Invoice" table="INVOICES">
<id name="Id" column="INV_ID" type="Int32" unsaved-value="0">
<generator class="sequence-identity">
<param name="sequence">INV_ID_SEQ</param>
</generator>
</id>
<property name="InvoiceDate" column="INV_DT" type="DateTime" not-null="true" />
<property name="Number" column="INV_NUMBER" type="String" length="12" not-null="true" />
<property name="Total" column="INV_TOTAL" type="decimal" not-null="true" />
<property name="Creator" column="INV_CREATOR_USR_ID" type="String" length="15" not-null="true" />
<property name="Created" column="INV_CREATED_DT" type="DateTime" not-null="true" />
<property name="Updater" column="INV_UPDATER_USR_ID" type="String" length="15" />
<property name="Updated" column="INV_UPDATED_DT" type="DateTime" not-null="false" />
<property name="Deleted" column="INV_DELETED_DT" type="DateTime" not-null="false" />
<many-to-one name="Customer" class="Customer" column="CUST_ID" />
</class>
</hibernate-mapping>

话虽如此,我想知道是否还有其他更好的方法可以做到这一点,因为实际上,在CustomerRepository中,我需要覆盖BaseRepository.Save()方法,只需如下调用InvoiceRepository.Save()方法:

public class CustomerRepository : BaseRepository<Customer> {
public CustomerRepository(ISession session) : base(session) { }
public override Customer Save(Customer instance) {
instance = base.Save(instance);
var invoices = new InvoiceRepository(session);
instance.Invoices.ToList().ForEach(inv => {
inv.Customer = instance;
invoices.Save(inv)
});
}
}
public class InvoiceRepository : BaseRepository<Invoice> {
public InvoiceRepository(ISession session) : base(session) { }        
}

此外,我想知道发票是否有可能"知道"谁是客户,而不必在保存时分配客户财产,并让NHibernate魔术为我工作?

在预事件中添加一个侦听器,并在NHibernate.Event命名空间中实现IPreDeleteEventListenerIPreInsertEventListenerIPreUpdateEventListener中的任意一个。

Ayende Raheen的一个简洁的例子:NHibernate IPreUpdateEventListener&IPreInsertEventListener。

public class AuditEventListener : IPreInsertEventListener, IPreUpdateEventListener {
public bool OnPreInsert(OnPreInsert @event) {
var audit = @event.Entity as IHaveAuditInformation;
if (audit == null) return false;
var time = DateTime.Now;
var name = WindowsIdentity.GetCurrent().Name;
Set(@event.Persister, @event.State, "CreatedAt", time);
Set(@event.Persister, @event.State, "CreatedBy", name);
audit.CreatedAt = time;
audit.CreatedBy = name;
return false;
}
public bool OnPreUpdate(OnPreUpdate @event) {
var audit = @event.Entity as IHaveAuditInformation;
if (audit == null) return false;
var time = DateTime.Now;
var name = WindowsIndentity.GetCurrent().Name;
Set(@event.Persister, @event.State, "UpdatedAt", time);
Set(@event.Persister, @event.State, "UpdatedBy", name);
audit.UpdatedAt= time;
audit.UpdatedBy = name;
return false;
}
}

CCD_ 24也可以做到这一点。

请注意返回值false。这实际上应该是两个OnPreEventResult枚举值之一。

  • OnPreEventResult.Continue(错误)
  • OnPreEventResult.Break(真)

根据@Radim Köhler对此问题的回答:

  • false/true对IPreInsertEventListeners到底意味着什么

因此,由于枚举不存在,我更喜欢通过另一个方法调用返回布尔值,而不是返回truefalse,它实际上明确地说明了它的作用。

private bool AbortOperation() { return true; }
private bool ContinueOperation() { return false; }

并将CCD_ 31替换为CCD_ 32。这使得代码更加清晰,并揭示了事件前方法的确切意图和行为。

接口实现后,只需将侦听器添加到配置中即可。

var listener = new AuditEventListener();
Configuration cfg = new Configuration();
c.SetListener(ListenerType.PreDelete, listener);
c.SetListener(ListenerType.PreInsert, listener);
c.SetListener(ListenerType.PreUpdate, listener);

现在,只需要使用cascade="all"映射属性,对ISession.SaveOrUpdate()进行一次干净的调用,就完成了!

相关内容

  • 没有找到相关文章

最新更新