使用NHibernate级联可追溯性?

时间:2014-02-17 15:28:47

标签: c# nhibernate inverse nhibernate-cascade traceability

我已阅读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魔法为我工作?

1 个答案:

答案 0 :(得分:1)

在事件前添加一个侦听器,并执行自定义逻辑,在IPreDeleteEventListener命名空间内实现IPreInsertEventListenerIPreUpdateEventListenerNHibernate.Event之一。

Ayende Rahien的一个简洁的例子: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;
    }
}

使用IPreDeleteEventListener也可以这样做。

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

  • OnPreEventResult.Continue(false)
  • OnPreEventResult.Break(true)

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

因此,由于枚举不存在,而不是返回truefalse,我更倾向于通过另一个方法调用返回布尔值,该调用实际上说明了它的含义。

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

return false替换为return ContinueOperation()。这使得代码更清晰,并揭示了事件前方法的确切意图和行为。

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

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

现在,我们要做的只是干净地调用ISession.SaveOrUpdate(),使用cascade="all"映射属性,然后就完成了!