【问题标题】:Cascading with traceability using NHibernate?使用 NHibernate 实现可追溯性级联?
【发布时间】:2014-02-17 15:28:46
【问题描述】:

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

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

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

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

因为我需要用户登录,我觉得和业务模型无关,所以我在repository的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() 方法只是为了调用@987654345 @方法如下:

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

【问题讨论】:

    标签: c# nhibernate inverse nhibernate-cascade traceability


    【解决方案1】:

    为前置事件添加一个侦听器并执行您的自定义逻辑,在 NHibernate.Event 命名空间内实现 IPreDeleteEventListenerIPreInsertEventListenerIPreUpdateEventListener 之一。

    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对这个问题的回答:

    因此,由于枚举不存在,而不是返回 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" 映射属性,就完成了!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-07-07
      • 2018-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多