Author Login
Post Reply
Here's the patch file for the aforementioned addition.
--
Attached: Patch File, VetoInterceptor1.patch
Patch Contents:
Updates to core NHibernate-2.0:
AbstractSaveEventListener, DefaultDeleteEventListener,
DefaultFlushEntityEventListener,
DefaultSaveOrUpdateEventListener, IInterceptor, EmptyInterceptor
Addition of Test Case: NHSpecificTest/InterceptVeto
Updates to Documentation: manipulating_data.xml //includes a rewrite
of the Interceptor section to include these additions, recommend
EmptyInterceptor instead of IInterceptor, and conversion of "boolean"
to "bool".
--
-Will Shaver
Index: doc/reference/modules/manipulating_data.xml
===================================================================
--- doc/reference/modules/manipulating_data.xml (revision 3271)
+++ doc/reference/modules/manipulating_data.xml (working copy)
@@(protected) @@
<title>Interceptors</title>
<para>
The <literal>IInterceptor</literal> interface provides callbacks from the session to the
- application allowing the application to inspect and / or manipulate properties of a
- persistent object before it is saved, updated, deleted or loaded. One
- possible use for this is to track auditing information. For example, the following
- <literal>IInterceptor</literal> automatically sets the <literal>CreateTimestamp</literal>
+ application allowing the application to inspect, manipulate, and reject changes to properties of a
+ persistent object before it is saved, updated, deleted or loaded.
+ </para>
+ <para>
+ The <literal>EmptyInterceptor</literal> class implements <literal>IInterceptor</literal> with default
+ values, allowing developers to easily implement a subset of all available methods. Extending the
+ <literal>EmptyInterceptor</literal> class is also recommended to reduce code changes required by
+ future additions to the <literal>IInterceptor</literal> interface.
+ </para>
+ <para>
+ One possible use for this is to track auditing information. For example, the following extension
+ of the <literal>EmptyInterceptor</literal> automatically sets the <literal>CreateTimestamp</literal>
when an <literal>IAuditable</literal> is created and updates the
<literal>LastUpdateTimestamp</literal> property when an <literal>IAuditable</literal> is
- updated.
+ updated. It also performs a logical deletion by setting the <literal>DateDeletedTimestamp</literal>
+ property and returning <literal>DeleteVeto.VetoWithCascade</literal> when an <literal>IAuditable</literal>
+ is deleted. Returning <literal>DeleteVeto.VetoWithCascade</literal> allows additional deletions normally caused
+ by <literal>cascade="delete"</literal> in the mapping file to continue, thereby allowing NHibernate to
</para>
-
+
<programlisting><![CDATA[using System;
using NHibernate.Type;
+using NHibernate;
namespace NHibernate.Test
{
[Serializable]
- public class AuditInterceptor : IInterceptor
+ public class AuditInterceptor : EmptyInterceptor
{
- private int updates;
- private int creates;
+ private int creates, updates, deletes;
- public void OnDelete(object entity,
- object id,
- object[] state,
- string[] propertyNames,
- IType[] types)
+ public override InterceptVeto OnBeforeDelete(object entity)
{
- // do nothing
+ if ( entity is IAuditable )
+ {
+ deletes++;
+ ((IAuditable) entity).DateDeletedTimestamp = DateTime.Now;
+ return InterceptVeto.VetoWithCascade;
+ }
+ return InterceptVeto.NoVeto;
}
- public boolean OnFlushDirty(object entity,
- object id,
- object[] currentState,
- object[] previousState,
- string[] propertyNames,
- IType[] types) {
-
+ public override bool OnFlushDirty(object entity, object id,
+ object[] currentState, object[] previousState,
+ string[] propertyNames, IType[] types)
+ {
if ( entity is IAuditable )
{
updates++;
@@(protected) @@
return false;
}
- public boolean OnLoad(object entity,
- object id,
- object[] state,
- string[] propertyNames,
- IType[] types)
+ public override bool OnSave(object entity, object id, object[] state,
+ string[] propertyNames, IType[] types)
{
- return false;
- }
-
- public boolean OnSave(object entity,
- object id,
- object[] state,
- string[] propertyNames,
- IType[] types)
- {
if ( entity is IAuditable )
{
creates++;
@@(protected) @@
return false;
}
- public void PostFlush(ICollection entities)
+ public override void PostFlush(ICollection entities)
{
- Console.Out.WriteLine("Creations: {0}, Updates: {1}", creates, updates);
+ Console.WriteLine("Creations: {0}, Updates: {1}, Deletes: {2}",
+ creates, updates, deletes);
}
- public void PreFlush(ICollection entities) {
- updates=0;
+ public override void PreFlush(ICollection entities)
+ {
creates=0;
+ updates=0;
+ deletes=0;
}
-
- ......
- ......
}
}]]></programlisting>
Index: src/NHibernate.Test/NHibernate.Test-2.0.csproj
===================================================================
--- src/NHibernate.Test/NHibernate.Test-2.0.csproj (revision 3271)
+++ src/NHibernate.Test/NHibernate.Test-2.0.csproj (working copy)
@@(protected) @@
<Compile Include="NHSpecificTest\CollectionFixture.cs" />
<Compile Include="NHSpecificTest\HqlOnMapWithForumula\Domain.cs" />
<Compile Include="NHSpecificTest\HqlOnMapWithForumula\Fixture.cs" />
+ <Compile Include="NHSpecificTest\InterceptVeto\Employee.cs" />
+ <Compile Include="NHSpecificTest\InterceptVeto\Employer.cs" />
+ <Compile Include="NHSpecificTest\InterceptVeto\Fixture.cs" />
<Compile Include="NHSpecificTest\ListsWithHoles\Employee.cs" />
<Compile Include="NHSpecificTest\ListsWithHoles\Fixture.cs" />
<Compile Include="NHSpecificTest\LoadingNullEntityInSet\Employee.cs" />
@@(protected) @@
<Service Include="{B4F97281-0DBD-4835-9ED8-7DFB966E87FF}" />
</ItemGroup>
<ItemGroup>
+ <EmbeddedResource Include="NHSpecificTest\InterceptVeto\Mappings.hbm.xml" />
+ </ItemGroup>
+ <ItemGroup>
<Folder Include="Properties\" />
<Folder Include="Unionsubclass2\" />
</ItemGroup>
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employer.cs
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employer.cs (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employer.cs (revision 0)
@@(protected) @@
+using System;
+using System.Collections;
+
+namespace NHibernate.Test.NHSpecificTest.InterceptVeto
+{
+ public class Employer
+ {
+ private int id;
+ private string name;
+ private IList employees = new ArrayList();
+
+ public Employer()
+ {
+ }
+
+ public Employer(string name)
+ {
+ this.name = name;
+ }
+
+ public int Id
+ {
+ get { return id; }
+ set { id = value; }
+ }
+
+ public string Name
+ {
+ get { return name; }
+ set { name = value; }
+ }
+
+ public IList Employees
+ {
+ get { return employees; }
+ set { employees = value; }
+ }
+
+ public void AddEmployee(Employee employee)
+ {
+ employees.Add(employee);
+ employee.Employer = this;
+ }
+ }
+}
\ No newline at end of file
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Mappings.hbm.xml
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Mappings.hbm.xml (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Mappings.hbm.xml (revision 0)
@@(protected) @@
+<?xml version="1.0" encoding="utf-8" ?>
+<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHibernate.Test.NHSpecificTest.InterceptVeto" assembly="NHibernate.Test" >
+ <class name="Employer" lazy="false">
+ <id name="Id">
+ <generator class="native" />
+ </id>
+ <property name="Name" />
+ <bag name="Employees" cascade="all" inverse="true">
+ <key column="EMR_ID" />
+ <one-to-many class="Employee"/>
+ </bag>
+ </class>
+
+ <class name="Employee" table="Employees" lazy="false">
+ <id name="Id">
+ <generator class="native" />
+ </id>
+ <property name="Name" />
+ <many-to-one name="Employer" column="EMR_ID" />
+ </class>
+</hibernate-mapping>
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employee.cs
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employee.cs (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employee.cs (revision 0)
@@(protected) @@
+using System;
+
+namespace NHibernate.Test.NHSpecificTest.InterceptVeto
+{
+ public class Employee
+ {
+ private int id;
+ private string name;
+ private Employer employer;
+
+ public Employee()
+ {
+ }
+
+ public Employee(string name)
+ {
+ this.name = name;
+ }
+
+ public int Id
+ {
+ get { return id; }
+ set { id = value; }
+ }
+
+ public string Name
+ {
+ get { return name; }
+ set { name = value; }
+ }
+
+ public Employer Employer
+ {
+ get { return employer; }
+ set { employer = value; }
+ }
+ }
+}
\ No newline at end of file
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Fixture.cs
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Fixture.cs (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Fixture.cs (revision 0)
@@(protected) @@
+using System;
+using System.Collections;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.InterceptVeto
+{
+ [TestFixture]
+ public class InterceptVetoFigure : BugTestCase
+ {
+
+ protected void StandardSetUp()
+ {
+ using(ISession session = OpenSession())
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ Employer emr = new Employer("Test Employer 1");
+ Employee[] employees = new Employee[2];
+ for(int i = 0; i < employees.Length; i++)
+ {
+ employees[i] = new Employee("Test Employee " + (i + 1).ToString());
+ emr.AddEmployee(employees[i]);
+
+ }
+ session.Save(emr);
+ tx.Commit();
+ }
+ }
+ }
+ protected override void OnTearDown()
+ {
+ base.OnTearDown();
+ using(ISession session = OpenSession())
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ session.Delete("from Employee");
+ session.Delete("from Employer");
+ tx.Commit();
+ }
+ }
+ }
+
+ [Test]
+ public void NoVeto_ShouldInsertUpdateDeleteEverything()
+ {
+ StandardSetUp();
+ //note that the EmptyInterceptor returns NoVeto from all three OnBefore... methods
+ using(ISession session = OpenSession())
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Assert.AreEqual(1, employers.Count, "Persisting an employer failed.");
+ Employer emr = employers[0] as Employer;
+ Assert.AreEqual(2, emr.Employees.Count, "Persisting cascaded employees failed");
+ emr.Name = "Big Pizza";
+ ((Employee)emr.Employees[0]).Name = "Joe";
+ ((Employee)emr.Employees[1]).Name = "Fred";
+ tx.Commit();
+ }
+ }
+
+ using(ISession session = OpenSession())
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Employer emr = employers[0] as Employer;
+ Assert.AreEqual("Big Pizza", emr.Name, "Saving a change on employer failed.");
+ Employee emp0, emp1;
+ if(((Employee)emr.Employees[0]).Name == "Joe")
+ {
+ emp0 = (Employee)emr.Employees[0];
+ emp1 = (Employee)emr.Employees[1];
+ }
+ else // just in case the order changes, bag order not guaranteed
+ {
+ emp0 = (Employee)emr.Employees[1];
+ emp1 = (Employee)emr.Employees[0];
+ }
+ Assert.AreEqual("Joe", emp0.Name, "Saving cascaded employees failed");
+ Assert.AreEqual("Fred", emp1.Name, "Saving cascaded employees failed");
+ session.Delete(emr); //this is needed for next two asserts
+ tx.Commit();
+ }
+ }
+ using(ISession session = OpenSession())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Assert.AreEqual(0, employers.Count, "Deleting an employer failed.");
+ IList employees = session.CreateQuery("select e from Employee as e").List();
+ Assert.AreEqual(0, employees.Count, "Cascaded delete of employees failed.");
+ }
+ }
+
+ [Test]
+ public void OnBeforeDelete_VetoNoCascade_ShouldDeleteNothing()
+ {
+ StandardSetUp();
+ VetoNoCascadeInterceptor interceptor = new VetoNoCascadeInterceptor();
+ using(ISession session = sessions.OpenSession(interceptor))
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Employer emp = employers[0] as Employer;
+ interceptor.entityToCheck = emp;
+ session.Delete(emp);
+ tx.Commit();
+ }
+ }
+ using(ISession session = OpenSession())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Assert.AreEqual(1, employers.Count, "VetoNoCascade failed to cancel a delete of an employer.");
+ IList employees = session.CreateQuery("select e from Employee as e").List();
+ Assert.AreEqual(2, employees.Count, "VetoNoCascade failed to cancel a delete of an employee");
+ }
+ }
+
+ [Test]
+ public void OnBeforeDelete_VetoWithCascade_ShouldDeleteChildren()
+ {
+ StandardSetUp();
+ VetoWithCascadeInterceptor interceptor = new VetoWithCascadeInterceptor();
+ using(ISession session = sessions.OpenSession(interceptor))
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Employer emp = employers[0] as Employer;
+ interceptor.entityToCheck = emp;
+ session.Delete(emp);
+ emp.Employees.Clear();
+ tx.Commit();
+ }
+ }
+ using(ISession session = OpenSession())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Assert.AreEqual(1, employers.Count, "VetoWithCascade failed to cancel a delete of an employer.");
+ IList employees = session.CreateQuery("select e from Employee as e").List();
+ Assert.AreEqual(0, employees.Count, "VetoWithCascade failed to delete all employees");
+ }
+ }
+
+ [Test]
+ public void OnBeforeInsert_VetoNoCascade_ShouldSaveNothing()
+ {
+ VetoNoCascadeInterceptor interceptor = new VetoNoCascadeInterceptor();
+ using(ISession session = sessions.OpenSession(interceptor))
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ Employer emr = new Employer("Test Employer 1");
+ Employee[] employees = new Employee[2];
+ for(int i = 0; i < employees.Length; i++)
+ {
+ employees[i] = new Employee("Test Employee " + (i + 1).ToString());
+ emr.AddEmployee(employees[i]);
+ }
+ interceptor.entityToCheck = emr;
+ session.Save(emr);
+ tx.Commit();
+ }
+ }
+ using(ISession session = OpenSession())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Assert.AreEqual(0, employers.Count, "VetoNoCascade saved an employer.");
+ IList employees = session.CreateQuery("select e from Employee as e").List();
+ Assert.AreEqual(0, employees.Count, "VetoNoCascade saved an employee");
+ }
+ }
+
+ [Test]
+ public void OnBeforeInsert_VetoWithCascade_ShouldSaveChildren()
+ {
+ VetoWithCascadeInterceptor interceptor = new VetoWithCascadeInterceptor();
+ using(ISession session = sessions.OpenSession(interceptor))
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ Employer emr = new Employer("Test Employer 1");
+ Employee[] employees = new Employee[2];
+ for(int i = 0; i < employees.Length; i++)
+ {
+ employees[i] = new Employee("Test Employee " + (i + 1).ToString());
+ emr.Employees.Add(employees[i]);
+ //emr.AddEmployee(employees[i]); // can't do this or foreign keys will fail, this only
+ //works when the foreign key field can be null.
+ }
+ interceptor.entityToCheck = emr;
+ session.Save(emr);
+ tx.Commit();
+ }
+ }
+ using(ISession session = OpenSession())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Assert.AreEqual(0, employers.Count, "VetoWithCascade saved an employer.");
+ IList employees = session.CreateQuery("select e from Employee as e").List();
+ Assert.AreEqual(2, employees.Count, "VetoNoCascade didn't save the employees");
+ }
+ }
+
+ [Test]
+ public void OnBeforeUpdate_Veto_ShouldNotUpdate()
+ {
+ StandardSetUp();
+ UpdateVetoInterceptor interceptor = new UpdateVetoInterceptor();
+ using(ISession session = sessions.OpenSession(interceptor))
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Employer emr = employers[0] as Employer;
+ emr.Name = "Big Pizza";
+ ((Employee)emr.Employees[0]).Name = "Joe";
+ ((Employee)emr.Employees[1]).Name = "Fred";
+ tx.Commit();
+ }
+ }
+
+ using(ISession session = OpenSession())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Employer emr = employers[0] as Employer;
+ Assert.AreNotEqual("Big Pizza", emr.Name, "Vetoed change was saved on an Employer.");
+ Employee emp0, emp1;
+ if(((Employee)emr.Employees[0]).Name == "Joe")
+ {
+ emp0 = (Employee)emr.Employees[0];
+ emp1 = (Employee)emr.Employees[1];
+ }
+ else // just in case the order changes, bag order not guaranteed
+ {
+ emp0 = (Employee)emr.Employees[1];
+ emp1 = (Employee)emr.Employees[0];
+ }
+ Assert.AreNotEqual("Joe", emp0.Name, "Vetoed change was saved on an Employeee.");
+ Assert.AreNotEqual("Fred", emp1.Name, "Vetoed change was saved on an Employeee.");
+ }
+ }
+
+ }
+
+ public class VetoNoCascadeInterceptor : EmptyInterceptor
+ {
+ public object entityToCheck;
+
+ public override NHibernate.DeleteVeto OnBeforeDelete(object entity)
+ {
+ if(entity == entityToCheck) { return NHibernate.DeleteVeto.VetoNoCascade; }
+ return NHibernate.DeleteVeto.NoVeto;
+ }
+
+ public override NHibernate.InsertVeto OnBeforeInsert(object entity)
+ {
+ if(entity == entityToCheck) { return NHibernate.InsertVeto.VetoNoCascade; }
+ return NHibernate.InsertVeto.NoVeto;
+ }
+ }
+ public class VetoWithCascadeInterceptor : EmptyInterceptor
+ {
+ public object entityToCheck;
+
+ public override NHibernate.DeleteVeto OnBeforeDelete(object entity)
+ {
+ if(entity == entityToCheck) { return NHibernate.DeleteVeto.VetoWithCascade; }
+ return NHibernate.DeleteVeto.NoVeto;
+ }
+
+ public override NHibernate.InsertVeto OnBeforeInsert(object entity)
+ {
+ if(entity == entityToCheck) { return NHibernate.InsertVeto.VetoWithCascade; }
+ return NHibernate.InsertVeto.NoVeto;
+ }
+ }
+ public class UpdateVetoInterceptor : EmptyInterceptor
+ {
+ public override NHibernate.UpdateVeto OnBeforeUpdate(object entity)
+ {
+ return NHibernate.UpdateVeto.Veto;
+ }
+ }
+}
\ No newline at end of file
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employee.cs
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employee.cs (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employee.cs (revision 0)
@@(protected) @@
+using System;
+
+namespace NHibernate.Test.NHSpecificTest.InterceptVeto
+{
+ public class Employee
+ {
+ private int id;
+ private string name;
+ private Employer employer;
+
+ public Employee()
+ {
+ }
+
+ public Employee(string name)
+ {
+ this.name = name;
+ }
+
+ public int Id
+ {
+ get { return id; }
+ set { id = value; }
+ }
+
+ public string Name
+ {
+ get { return name; }
+ set { name = value; }
+ }
+
+ public Employer Employer
+ {
+ get { return employer; }
+ set { employer = value; }
+ }
+ }
+}
\ No newline at end of file
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employer.cs
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employer.cs (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Employer.cs (revision 0)
@@(protected) @@
+using System;
+using System.Collections;
+
+namespace NHibernate.Test.NHSpecificTest.InterceptVeto
+{
+ public class Employer
+ {
+ private int id;
+ private string name;
+ private IList employees = new ArrayList();
+
+ public Employer()
+ {
+ }
+
+ public Employer(string name)
+ {
+ this.name = name;
+ }
+
+ public int Id
+ {
+ get { return id; }
+ set { id = value; }
+ }
+
+ public string Name
+ {
+ get { return name; }
+ set { name = value; }
+ }
+
+ public IList Employees
+ {
+ get { return employees; }
+ set { employees = value; }
+ }
+
+ public void AddEmployee(Employee employee)
+ {
+ employees.Add(employee);
+ employee.Employer = this;
+ }
+ }
+}
\ No newline at end of file
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Fixture.cs
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Fixture.cs (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Fixture.cs (revision 0)
@@(protected) @@
+using System;
+using System.Collections;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.InterceptVeto
+{
+ [TestFixture]
+ public class InterceptVetoFigure : BugTestCase
+ {
+
+ protected void StandardSetUp()
+ {
+ using(ISession session = OpenSession())
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ Employer emr = new Employer("Test Employer 1");
+ Employee[] employees = new Employee[2];
+ for(int i = 0; i < employees.Length; i++)
+ {
+ employees[i] = new Employee("Test Employee " + (i + 1).ToString());
+ emr.AddEmployee(employees[i]);
+
+ }
+ session.Save(emr);
+ tx.Commit();
+ }
+ }
+ }
+ protected override void OnTearDown()
+ {
+ base.OnTearDown();
+ using(ISession session = OpenSession())
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ session.Delete("from Employee");
+ session.Delete("from Employer");
+ tx.Commit();
+ }
+ }
+ }
+
+ [Test]
+ public void NoVeto_ShouldInsertUpdateDeleteEverything()
+ {
+ StandardSetUp();
+ //note that the EmptyInterceptor returns NoVeto from all three OnBefore... methods
+ using(ISession session = OpenSession())
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Assert.AreEqual(1, employers.Count, "Persisting an employer failed.");
+ Employer emr = employers[0] as Employer;
+ Assert.AreEqual(2, emr.Employees.Count, "Persisting cascaded employees failed");
+ emr.Name = "Big Pizza";
+ ((Employee)emr.Employees[0]).Name = "Joe";
+ ((Employee)emr.Employees[1]).Name = "Fred";
+ tx.Commit();
+ }
+ }
+
+ using(ISession session = OpenSession())
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Employer emr = employers[0] as Employer;
+ Assert.AreEqual("Big Pizza", emr.Name, "Saving a change on employer failed.");
+ Employee emp0, emp1;
+ if(((Employee)emr.Employees[0]).Name == "Joe")
+ {
+ emp0 = (Employee)emr.Employees[0];
+ emp1 = (Employee)emr.Employees[1];
+ }
+ else // just in case the order changes, bag order not guaranteed
+ {
+ emp0 = (Employee)emr.Employees[1];
+ emp1 = (Employee)emr.Employees[0];
+ }
+ Assert.AreEqual("Joe", emp0.Name, "Saving cascaded employees failed");
+ Assert.AreEqual("Fred", emp1.Name, "Saving cascaded employees failed");
+ session.Delete(emr); //this is needed for next two asserts
+ tx.Commit();
+ }
+ }
+ using(ISession session = OpenSession())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Assert.AreEqual(0, employers.Count, "Deleting an employer failed.");
+ IList employees = session.CreateQuery("select e from Employee as e").List();
+ Assert.AreEqual(0, employees.Count, "Cascaded delete of employees failed.");
+ }
+ }
+
+ [Test]
+ public void OnBeforeDelete_VetoNoCascade_ShouldDeleteNothing()
+ {
+ StandardSetUp();
+ VetoNoCascadeInterceptor interceptor = new VetoNoCascadeInterceptor();
+ using(ISession session = sessions.OpenSession(interceptor))
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Employer emp = employers[0] as Employer;
+ interceptor.entityToCheck = emp;
+ session.Delete(emp);
+ tx.Commit();
+ }
+ }
+ using(ISession session = OpenSession())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Assert.AreEqual(1, employers.Count, "VetoNoCascade failed to cancel a delete of an employer.");
+ IList employees = session.CreateQuery("select e from Employee as e").List();
+ Assert.AreEqual(2, employees.Count, "VetoNoCascade failed to cancel a delete of an employee");
+ }
+ }
+
+ [Test]
+ public void OnBeforeDelete_VetoWithCascade_ShouldDeleteChildren()
+ {
+ StandardSetUp();
+ VetoWithCascadeInterceptor interceptor = new VetoWithCascadeInterceptor();
+ using(ISession session = sessions.OpenSession(interceptor))
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Employer emp = employers[0] as Employer;
+ interceptor.entityToCheck = emp;
+ session.Delete(emp);
+ emp.Employees.Clear();
+ tx.Commit();
+ }
+ }
+ using(ISession session = OpenSession())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Assert.AreEqual(1, employers.Count, "VetoWithCascade failed to cancel a delete of an employer.");
+ IList employees = session.CreateQuery("select e from Employee as e").List();
+ Assert.AreEqual(0, employees.Count, "VetoWithCascade failed to delete all employees");
+ }
+ }
+
+ [Test]
+ public void OnBeforeInsert_VetoNoCascade_ShouldSaveNothing()
+ {
+ VetoNoCascadeInterceptor interceptor = new VetoNoCascadeInterceptor();
+ using(ISession session = sessions.OpenSession(interceptor))
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ Employer emr = new Employer("Test Employer 1");
+ Employee[] employees = new Employee[2];
+ for(int i = 0; i < employees.Length; i++)
+ {
+ employees[i] = new Employee("Test Employee " + (i + 1).ToString());
+ emr.AddEmployee(employees[i]);
+ }
+ interceptor.entityToCheck = emr;
+ session.Save(emr);
+ tx.Commit();
+ }
+ }
+ using(ISession session = OpenSession())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Assert.AreEqual(0, employers.Count, "VetoNoCascade saved an employer.");
+ IList employees = session.CreateQuery("select e from Employee as e").List();
+ Assert.AreEqual(0, employees.Count, "VetoNoCascade saved an employee");
+ }
+ }
+
+ [Test]
+ public void OnBeforeInsert_VetoWithCascade_ShouldSaveChildren()
+ {
+ VetoWithCascadeInterceptor interceptor = new VetoWithCascadeInterceptor();
+ using(ISession session = sessions.OpenSession(interceptor))
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ Employer emr = new Employer("Test Employer 1");
+ Employee[] employees = new Employee[2];
+ for(int i = 0; i < employees.Length; i++)
+ {
+ employees[i] = new Employee("Test Employee " + (i + 1).ToString());
+ emr.Employees.Add(employees[i]);
+ //emr.AddEmployee(employees[i]); // can't do this or foreign keys will fail, this only
+ //works when the foreign key field can be null.
+ }
+ interceptor.entityToCheck = emr;
+ session.Save(emr);
+ tx.Commit();
+ }
+ }
+ using(ISession session = OpenSession())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Assert.AreEqual(0, employers.Count, "VetoWithCascade saved an employer.");
+ IList employees = session.CreateQuery("select e from Employee as e").List();
+ Assert.AreEqual(2, employees.Count, "VetoNoCascade didn't save the employees");
+ }
+ }
+
+ [Test]
+ public void OnBeforeUpdate_Veto_ShouldNotUpdate()
+ {
+ StandardSetUp();
+ UpdateVetoInterceptor interceptor = new UpdateVetoInterceptor();
+ using(ISession session = sessions.OpenSession(interceptor))
+ {
+ using(ITransaction tx = session.BeginTransaction())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Employer emr = employers[0] as Employer;
+ emr.Name = "Big Pizza";
+ ((Employee)emr.Employees[0]).Name = "Joe";
+ ((Employee)emr.Employees[1]).Name = "Fred";
+ tx.Commit();
+ }
+ }
+
+ using(ISession session = OpenSession())
+ {
+ IList employers = session.CreateQuery("select e from Employer as e").List();
+ Employer emr = employers[0] as Employer;
+ Assert.AreNotEqual("Big Pizza", emr.Name, "Vetoed change was saved on an Employer.");
+ Employee emp0, emp1;
+ if(((Employee)emr.Employees[0]).Name == "Joe")
+ {
+ emp0 = (Employee)emr.Employees[0];
+ emp1 = (Employee)emr.Employees[1];
+ }
+ else // just in case the order changes, bag order not guaranteed
+ {
+ emp0 = (Employee)emr.Employees[1];
+ emp1 = (Employee)emr.Employees[0];
+ }
+ Assert.AreNotEqual("Joe", emp0.Name, "Vetoed change was saved on an Employeee.");
+ Assert.AreNotEqual("Fred", emp1.Name, "Vetoed change was saved on an Employeee.");
+ }
+ }
+
+ }
+
+ public class VetoNoCascadeInterceptor : EmptyInterceptor
+ {
+ public object entityToCheck;
+
+ public override NHibernate.DeleteVeto OnBeforeDelete(object entity)
+ {
+ if(entity == entityToCheck) { return NHibernate.DeleteVeto.VetoNoCascade; }
+ return NHibernate.DeleteVeto.NoVeto;
+ }
+
+ public override NHibernate.InsertVeto OnBeforeInsert(object entity)
+ {
+ if(entity == entityToCheck) { return NHibernate.InsertVeto.VetoNoCascade; }
+ return NHibernate.InsertVeto.NoVeto;
+ }
+ }
+ public class VetoWithCascadeInterceptor : EmptyInterceptor
+ {
+ public object entityToCheck;
+
+ public override NHibernate.DeleteVeto OnBeforeDelete(object entity)
+ {
+ if(entity == entityToCheck) { return NHibernate.DeleteVeto.VetoWithCascade; }
+ return NHibernate.DeleteVeto.NoVeto;
+ }
+
+ public override NHibernate.InsertVeto OnBeforeInsert(object entity)
+ {
+ if(entity == entityToCheck) { return NHibernate.InsertVeto.VetoWithCascade; }
+ return NHibernate.InsertVeto.NoVeto;
+ }
+ }
+ public class UpdateVetoInterceptor : EmptyInterceptor
+ {
+ public override NHibernate.UpdateVeto OnBeforeUpdate(object entity)
+ {
+ return NHibernate.UpdateVeto.Veto;
+ }
+ }
+}
\ No newline at end of file
Index: src/NHibernate.Test/NHSpecificTest/InterceptVeto/Mappings.hbm.xml
===================================================================
--- src/NHibernate.Test/NHSpecificTest/InterceptVeto/Mappings.hbm.xml (revision 0)
+++ src/NHibernate.Test/NHSpecificTest/InterceptVeto/Mappings.hbm.xml (revision 0)
@@(protected) @@
+<?xml version="1.0" encoding="utf-8" ?>
+<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHibernate.Test.NHSpecificTest.InterceptVeto" assembly="NHibernate.Test" >
+ <class name="Employer" lazy="false">
+ <id name="Id">
+ <generator class="native" />
+ </id>
+ <property name="Name" />
+ <bag name="Employees" cascade="all" inverse="true">
+ <key column="EMR_ID" />
+ <one-to-many class="Employee"/>
+ </bag>
+ </class>
+
+ <class name="Employee" table="Employees" lazy="false">
+ <id name="Id">
+ <generator class="native" />
+ </id>
+ <property name="Name" />
+ <many-to-one name="Employer" column="EMR_ID" />
+ </class>
+</hibernate-mapping>
Index: src/NHibernate/EmptyInterceptor.cs
===================================================================
--- src/NHibernate/EmptyInterceptor.cs (revision 3271)
+++ src/NHibernate/EmptyInterceptor.cs (working copy)
@@(protected) @@
{
return sql;
}
+
+ public virtual DeleteVeto OnBeforeDelete(object entity)
+ {
+ return DeleteVeto.NoVeto;
+ }
+
+ public virtual InsertVeto OnBeforeInsert(object entity)
+ {
+ return InsertVeto.NoVeto;
+ }
+
+ public virtual UpdateVeto OnBeforeUpdate(object entity)
+ {
+ return UpdateVeto.NoVeto;
+ }
}
}
\ No newline at end of file
Index: src/NHibernate/Event/Default/AbstractSaveEventListener.cs
===================================================================
--- src/NHibernate/Event/Default/AbstractSaveEventListener.cs (revision 3271)
+++ src/NHibernate/Event/Default/AbstractSaveEventListener.cs (working copy)
@@(protected) @@
//bool shouldDelayIdentityInserts = !inTxn && !requiresImmediateIdAccess;
bool shouldDelayIdentityInserts = false;
+ InsertVeto veto = source.Interceptor.OnBeforeInsert(entity);
+ if(veto == InsertVeto.VetoNoCascade)
+ {
+ if(log.IsDebugEnabled)
+ {
+ log.Debug("insert veto" + MessageHelper.InfoString(persister, id, source.Factory));
+ }
+ return id;
+ }
+
+ if(veto == InsertVeto.VetoWithCascade)
+ {
+ if(log.IsDebugEnabled)
+ {
+ log.Debug("insert veto with cascade" + MessageHelper.InfoString(persister, id, source.Factory));
+ }
+ CascadeBeforeSave(source, persister, entity, anything);
+ CascadeAfterSave(source, persister, entity, anything);
+ return id;
+ }
+
// Put a placeholder in entries, so we don't recurse back and try to save() the
// same object again. QUESTION: should this be done before onSave() is called?
// likewise, should it be done before onUpdate()?
Index: src/NHibernate/Event/Default/DefaultDeleteEventListener.cs
===================================================================
--- src/NHibernate/Event/Default/DefaultDeleteEventListener.cs (revision 3271)
+++ src/NHibernate/Event/Default/DefaultDeleteEventListener.cs (working copy)
@@(protected) @@
object[] deletedState = CreateDeletedState(persister, currentState, session);
entityEntry.DeletedState = deletedState;
- session.Interceptor.OnDelete(entity, entityEntry.Id, deletedState, persister.PropertyNames, propTypes);
-
- // before any callbacks, etc, so subdeletions see that this deletion happened first
- persistenceContext.SetEntryStatus(entityEntry, Status.Deleted);
+ DeleteVeto veto = session.Interceptor.OnBeforeDelete(entity);
+ switch(veto)
+ {
+ case DeleteVeto.NoVeto:
+ {
+ session.Interceptor.OnDelete(entity, entityEntry.Id, deletedState, persister.PropertyNames, propTypes);
+
+ // before any callbacks, etc, so subdeletions see that this deletion happened first
+ persistenceContext.SetEntryStatus(entityEntry, Status.Deleted);
+ break;
+ }
+ case DeleteVeto.VetoNoCascade:
+ {
+ if (log.IsDebugEnabled)
+ {
+ log.Debug("delete veto" + MessageHelper.InfoString(persister, entityEntry.Id, session.Factory));
+ }
+ return;
+ }
+ case DeleteVeto.VetoWithCascade:
+ {
+ if (log.IsDebugEnabled)
+ {
+ log.Debug("delete veto with cascade" + MessageHelper.InfoString(persister, entityEntry.Id, session.Factory));
+ }
+ break;
+ }
+ }
+
EntityKey key = new EntityKey(entityEntry.Id, persister, session.EntityMode);
CascadeBeforeDelete(session, persister, entity, entityEntry, transientEntities);
@@(protected) @@
new Nullability(session).CheckNullability(entityEntry.DeletedState, persister, true);
persistenceContext.NullifiableEntityKeys.Add(key);
- // Ensures that containing deletions happen before sub-deletions
- session.ActionQueue.AddAction(new EntityDeleteAction(entityEntry.Id, deletedState, version, entity, persister, isCascadeDeleteEnabled, session));
+ if (veto == DeleteVeto.NoVeto)
+ {
+ // Ensures that containing deletions happen before sub-deletions
+ session.ActionQueue.AddAction(new EntityDeleteAction(entityEntry.Id, deletedState, version, entity, persister, isCascadeDeleteEnabled, session));
+ }
CascadeAfterDelete(session, persister, entity, transientEntities);
Index: src/NHibernate/Event/Default/DefaultFlushEntityEventListener.cs
===================================================================
--- src/NHibernate/Event/Default/DefaultFlushEntityEventListener.cs (revision 3271)
+++ src/NHibernate/Event/Default/DefaultFlushEntityEventListener.cs (working copy)
@@(protected) @@
//TODO: avoid this for non-new instances where mightBeDirty==false
bool substitute = WrapCollections(session, persister, types, values);
+ if (session.Interceptor.OnBeforeUpdate(entity) == UpdateVeto.Veto)
+ {
+ if (log.IsDebugEnabled)
+ {
+ log.Debug("Vetoed an update of an entity: " + MessageHelper.InfoString(persister, entry.Id, session.Factory));
+ }
+ return;
+ }
+
if (IsUpdateNecessary(@(protected)))
{
substitute = ScheduleUpdate(@(protected);
Index: src/NHibernate/Event/Default/DefaultSaveOrUpdateEventListener.cs
===================================================================
--- src/NHibernate/Event/Default/DefaultSaveOrUpdateEventListener.cs (revision 3271)
+++ src/NHibernate/Event/Default/DefaultSaveOrUpdateEventListener.cs (working copy)
@@(protected) @@
}
}
+ /// <summary> Performs an update of a DETACHED entity. </summary>
+ /// <param name="event">The update event to be handled. </param>
+ /// <param name="entity">The entity. </param>
+ /// <param name="persister">The entity persister </param>
protected internal void PerformUpdate(SaveOrUpdateEvent @event, object entity, IEntityPersister persister)
{
if (!persister.IsMutable)
Index: src/NHibernate/IInterceptor.cs
===================================================================
--- src/NHibernate/IInterceptor.cs (revision 3271)
+++ src/NHibernate/IInterceptor.cs (working copy)
@@(protected) @@
bool OnSave(object entity, object id, object[] state, string[] propertyNames, IType[] types);
/// <summary>
- /// Called before an object is deleted
+ /// Called as an object is being deleted, after OnBeforeDelete has approved the decision by returning NoVeto.
/// </summary>
/// <param name="entity"></param>
/// <param name="id"></param>
+ /// <param name="state"></param>
/// <param name="propertyNames"></param>
- /// <param name="state"></param>
/// <param name="types"></param>
/// <remarks>
/// It is not recommended that the interceptor modify the <c>state</c>.
/// </remarks>
void OnDelete(object entity, object id, object[] state, string[] propertyNames, IType[] types);
+
/// <summary> Called before a collection is (re)created.</summary>
void OnCollectionRecreate(object collection, object key);
/// <summary> Called before a collection is deleted.</summary>
@@(protected) @@
/// <param name="sql">sql to be prepared </param>
/// <returns> original or modified sql </returns>
SqlString OnPrepareStatement(SqlString sql);
+
+ /// <summary>
+ /// Called before the object is flushed to the database, allowing the flush to be cancelled.
+ /// </summary>
+ /// <param name="entity">The entity to be inserted.</param>
+ /// <remarks>
+ /// This function allows the update to be cancelled. A veto (with cascade or without cascade) will cause the entity not to be persisted.
+ /// It is possible to cancel inserts that make needed changes to foreign key associations thereby causing exceptions.
+ /// </remarks>
+ /// <returns>InterceptVeto</returns>
+ InsertVeto OnBeforeInsert(object entity);
+
+ /// <summary>
+ /// Called before an object being saved, allowing the save to be cancelled.
+ /// </summary>
+ /// <param name="entity">The entity to be saved.</param>
+ /// <remarks>
+ /// This function allows the insert to be cancelled. A veto (with cascade or without cascade)
+ /// will keep the original version of the entity in the database.
+ /// Return InterceptVeto.VetoWithCascade to continue updating other objects but not this one.
+ /// It is possible to cancel updates that make needed changes to foreign key associations thereby causing exceptions.
+ /// </remarks>
+ /// <returns>InterceptVeto</returns>
+ UpdateVeto OnBeforeUpdate(object entity);
+
+ /// <summary>
+ /// Called before an object is deleted, allowing the delete to be cancelled.
+ /// </summary>
+ /// <param name="entity">The entity to be deleted.</param>
+ /// <remarks>
+ /// This function allows the delete to be cancelled. Modifications may be made to the object state.
+ /// Perform logical deletion by modifying the entity and saving it under the current session.
+ /// Return InterceptVeto.VetoWithCascade to continue delete other objects following a logical delete.
+ /// (Note that saving it will cause the OnSave event to fire, which will happen after other physical deletions in the cascade.)
+ /// OnBeforeDelete will fire after the OnDelete (deprecated) ILifecycle event.
+ /// </remarks>
+ /// <returns>InterceptVeto</returns>
+ DeleteVeto OnBeforeDelete(object entity);
}
+
+
+ public enum DeleteVeto
+ {
+ /// <summary>
+ /// Veto the delete with no further cascades
+ /// </summary>
+ VetoNoCascade,
+ /// <summary>
+ /// Veto the delete but cascade as though the delete had occured
+ /// </summary>
+ VetoWithCascade,
+ /// <summary>
+ /// Accept the delete
+ /// </summary>
+ NoVeto
+ }
+
+ public enum UpdateVeto
+ {
+ /// <summary>
+ /// Veto the update
+ /// </summary>
+ Veto,
+ /// <summary>
+ /// Accept the update
+ /// </summary>
+ NoVeto
+ }
+
+ public enum InsertVeto
+ {
+ /// <summary>
+ /// Veto the insert with no further cascades
+ /// </summary>
+ VetoNoCascade,
+ /// <summary>
+ /// Veto the insert but cascade as though the insert had occured
+ /// </summary>
+ VetoWithCascade,
+ /// <summary>
+ /// Accept the insert
+ /// </summary>
+ NoVeto
+ }
}
\ No newline at end of file
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Nhibernate-development mailing list
Nhibernate-development@(protected)
https://lists.sourceforge.net/lists/listinfo/nhibernate-development