0

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

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

假设我有两个类,Customer并且Invoice都需要可追溯性,TraceableEntity.

我正在为我的所有实体使用存储库模式,因此存储库被注入NHibernate.ISession到那里的构造函数中。事实上,我为每个实体都有一个存储库,Customer并且Invoice.

因为我需要用户登录,所以我认为这与业务模型无关,所以我将它设置在存储库的 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) { }        
}

另外,我想知道发票是否有可能“知道”谁是客户,而不必在保存时分配 Customer 属性,并让 NHibernate 魔术为我工作?

4

1 回答 1

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(错误的)
  • OnPreEventResult.Break(真的)

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

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

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

并替换return falseby 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"映射属性,你就完成了!

于 2014-02-18T14:57:43.807 回答