代码改变世界

NHibernate Step By Step(3)-数据操作回调方法(拦截器)

2009-08-02 16:25  Clingingboy  阅读(1114)  评论(2编辑  收藏  举报


1.ILifecycle
2.IValidatable
3.Interceptor

ILifecycle接口

在数据操作的时候,我们可能需要做些格外的处理,比如Save方法可能就需要一个OnSave方法

NH提供了ILifecycle接口,用于回调

该接口包含4个方法,当实体类实现该接口时,session调用这些方法时,将会先检测,以返回的LifecycleVeto枚举作判断,Veto则禁止通过,NoVeto则允许数据操作执行

image

如下实体类

public class EntityWithLifecycle : ILifecycle
    {
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
        public virtual double Heigth { get; set; }
        public virtual double Width { get; set; }
        public EntityWithLifecycle() {}
        public EntityWithLifecycle(string name, double heigth, double width)
        {
            Name = name;
            Heigth = heigth;
            Width = width;
        }

        public virtual LifecycleVeto OnSave(ISession s)
        {
            return IsValid() ? LifecycleVeto.NoVeto : LifecycleVeto.Veto;
        }

        public virtual LifecycleVeto OnUpdate(ISession s)
        {
            return IsValid() ? LifecycleVeto.NoVeto : LifecycleVeto.Veto;
        }

        public virtual LifecycleVeto OnDelete(ISession s)
        {
            return IsValid() ? LifecycleVeto.NoVeto : LifecycleVeto.Veto;
        }

        public virtual void OnLoad(ISession s, object id)
        {
            // nothing to do
        }

        public virtual IList<string> GetBrokenRules()
        {
            IList<string> result = new List<string>(3);
            if (string.IsNullOrEmpty(Name) || Name.Trim().Length < 2)
                result.Add("The Name must have more than one char.");
            if (Heigth <= 0)
                result.Add("Heigth must be great than 0");
            if (Width <= 0)
                result.Add("Width must be great than 0.");
            return result;
        }

        /// <summary>
        /// Validate the state of the object before persisting it. If a violation occurs,
        /// throw a <see cref="ValidationFailure" />. This method must not change the state of the object
        /// by side-effect.
        /// </summary>
        private bool IsValid()
        {
            IList<string> br = GetBrokenRules();
            return br == null || br.Count == 0;
        }
    }

测试,将无法保存数据到数据库中

using (ISession s = OpenSession())
{
    s.Save(new EntityWithLifecycle());
    s.Flush();
}

IValidatable接口

public interface IValidatable
{
    /// <summary>
    /// Validate the state of the object before persisting it. If a violation occurs,
    /// throw a <see cref="ValidationFailure" />. This method must not change the state of the object
    /// by side-effect.
    /// </summary>
    void Validate();
}

用于验证数据的正确性,实体实现该接口,在保存或更新数据操作时,都会调用此方法,若验证不通过,则抛出ValidationFailure错误

public class Video: IValidatable
    {
        private int id;
        private string name;
        private double heigth;
        private double width;

        public Video() {}

        public Video(string name, double heigth, double width)
        {
            this.name = name;
            this.heigth = heigth;
            this.width = width;
        }

        public virtual int Id
        {
            get { return id; }
            set { id = value; }
        }

        public virtual string Name
        {
            get { return name; }
            set { name = value; }
        }

        public virtual double Heigth
        {
            get { return heigth; }
            set { heigth = value; }
        }

        public virtual double Width
        {
            get { return width; }
            set { width = value; }
        }

        #region IValidatable Members
        public virtual IList<string> GetBrokenRules()
        {
            IList<string> result = new List<string>(3);
            if (string.IsNullOrEmpty(Name) || Name.Trim().Length < 2)
                result.Add("The Name must have more than one char.");
            if (Heigth <= 0)
                result.Add("Heigth must be great than 0");
            if (Width <= 0)
                result.Add("Width must be great than 0.");
            return result;
        }

        /// <summary>
        /// Validate the state of the object before persisting it. If a violation occurs,
        /// throw a <see cref="ValidationFailure" />. This method must not change the state of the object
        /// by side-effect.
        /// </summary>
        public virtual void Validate()
        {
            IList<string> br = GetBrokenRules();
            if (br != null && br.Count > 0)
                throw new ValidationFailure(BrokenRulesFormat(typeof(Video), br));
        }

        private static string BrokenRulesFormat(System.Type entity, IList<string> brokenRulesDescriptions)
        {
            if (entity == null)
                throw new ArgumentNullException("entity");
            if (brokenRulesDescriptions == null)
                throw new ArgumentNullException("brokenRulesDescriptions");

            StringBuilder sb = new StringBuilder(50 + brokenRulesDescriptions.Count * 50)
                .AppendLine(string.Format("Entity:{0}", entity));
            foreach (string message in brokenRulesDescriptions)
                sb.AppendLine(message);

            return sb.ToString();
        }

        #endregion
    }


测试,程序将跳入catch中

try
{
    using (ISession s = OpenSession())
    {
        s.Save(new Video());
        s.Flush();
    }
    Assert.Fail("Saved an invalid entity");
}
catch(ValidationFailure)
{
    // Ok
}

以上两种方法需要个别实体全部实现,若数量多的话就麻烦了,于是出现了拦截器

拦截器

image

比如对每次操作写入日志时,可使用此方法

使用拦截器方法
1.在session被创建时
ISession session = sf.OpenSession( new AuditInterceptor() );
2.在Configuration中声明
new Configuration().SetInterceptor( new AuditInterceptor() );

使用拦截器可以拦截到的数据进行修改,所以使用拦截器也需要小心,做的不好,可能会破坏原有程序

简单示例

public class StatefulInterceptor : EmptyInterceptor
    {
        private ISession session;
        private readonly IList list = new ArrayList();

        public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, IType[] types)
        {
            if (!(entity is Log))
            {
                list.Add(new Log("insert", (string) id, entity.GetType().FullName));
            }
            return false;
        }

        public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, IType[] types)
        {
        if ( !(entity is Log) ) {
            list.Add( new Log( "update", (string) id, entity.GetType().FullName ) );
        }
        return false;
        }

        public override void PostFlush(ICollection entities)
        {
            if (list.Count > 0)
            {
                foreach (object iter in list)
                {
                    session.Persist(iter);
                }
                list.Clear();
                session.Flush();
            }
        }

        public override void SetSession(ISession sessionLocal)
        {
            session = sessionLocal;
        }

        public ISession Session
        {
            get { return session; }
        }
    }

测试
StatefulInterceptor statefulInterceptor = new StatefulInterceptor();
ISession s = OpenSession(statefulInterceptor);
Assert.IsNotNull(statefulInterceptor.Session);

ITransaction t = s.BeginTransaction();
User u = new User("Gavin", "nivag");
s.Persist(u);
u.Password = "vagni";
t.Commit();
s.Close();

s = OpenSession();
t = s.BeginTransaction();
IList logs = s.CreateCriteria(typeof(Log)).List();
Assert.AreEqual(2, logs.Count);
s.Delete(u);
s.Delete("from Log");
t.Commit();
s.Close();