You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by an...@apache.org on 2009/11/18 13:08:21 UTC

svn commit: r881740 - in /cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src: main/java/org/apache/cayenne/ main/java/org/apache/cayenne/access/ main/java/org/apache/cayenne/reflect/ main/java/org/apache/cayenne/remote/ test/java/org/apache/ca...

Author: andrey
Date: Wed Nov 18 12:08:20 2009
New Revision: 881740

URL: http://svn.apache.org/viewvc?rev=881740&view=rev
Log:
CAY-1312 Allow lifecycle callbacks on ROP client. Only method invokes, i.e. no configuration for those callbacks yet

Added:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/DataChannelSyncCallbackAction.java
      - copied, changed from r881253, cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataChannelSyncCallbackAction.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/RemoteCallbacksTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/ClientMtLifecycles.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/MtLifecycles.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMtLifecycles.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MtLifecycles.java
Removed:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataChannelSyncCallbackAction.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContextDeleteAction.java
Modified:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ObjectContextDeleteAction.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataDomain.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackRegistry.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/remote/ClientChannel.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMultiTier.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MultiTier.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/multi-tier.map.xml

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java?rev=881740&r1=881739&r2=881740&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java Wed Nov 18 12:08:20 2009
@@ -33,6 +33,7 @@
 import org.apache.cayenne.graph.GraphEvent;
 import org.apache.cayenne.graph.GraphManager;
 import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.LifecycleEvent;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.query.ObjectIdQuery;
 import org.apache.cayenne.query.Query;
@@ -103,8 +104,6 @@
 
     public abstract void commitChangesToParent();
 
-    public abstract void deleteObject(Object object) throws DeleteDenyException;
-
     public abstract Collection<?> deletedObjects();
 
     public DataChannel getChannel() {
@@ -381,9 +380,24 @@
     }
     
     /**
-     * If ObjEntity qualifier is set, asks it to inject initial value to an object 
+     * If ObjEntity qualifier is set, asks it to inject initial value to an object.
+     * Also performs all Persistent initialization operations
      */
-    protected void injectInitialValue(Object object) {
+    protected void injectInitialValue(Object obj) {
+        // must follow this exact order of property initialization per CAY-653, i.e. have
+        // the id and the context in place BEFORE setPersistence is called
+        
+        Persistent object = (Persistent) obj;
+        
+        object.setObjectContext(this);
+        object.setPersistenceState(PersistenceState.NEW);
+        
+        GraphManager graphManager = getGraphManager();
+        synchronized (graphManager) {
+            graphManager.registerNode(object.getObjectId(), object);
+            graphManager.nodeCreated(object.getObjectId());
+        }
+        
         ObjEntity entity;
         try {
             entity = getEntityResolver().lookupObjEntity(object.getClass());
@@ -398,5 +412,37 @@
                 ((ValueInjector) entity.getDeclaredQualifier()).injectValue(object);
             }
         }
+        
+        // invoke callbacks
+        getEntityResolver().getCallbackRegistry().performCallbacks(
+                LifecycleEvent.POST_ADD,
+                object);
+    }
+    
+    /**
+     * Schedules an object for deletion on the next commit of this context. Object's
+     * persistence state is changed to PersistenceState.DELETED; objects related to this
+     * object are processed according to delete rules, i.e. relationships can be unset
+     * ("nullify" rule), deletion operation is cascaded (cascade rule).
+     * 
+     * @param object a persistent object that we want to delete.
+     * @throws DeleteDenyException if a DENY delete rule is applicable for object
+     *             deletion.
+     * @throws NullPointerException if object is null.
+     */
+    public void deleteObject(Object object) {
+        new ObjectContextDeleteAction(this).performDelete((Persistent) object);
+    }
+    
+    public void deleteObjects(Collection<?> objects) throws DeleteDenyException {
+        if (objects.isEmpty())
+            return;
+
+        // Don't call deleteObject() directly since it would be less efficient.
+        ObjectContextDeleteAction ocda = new ObjectContextDeleteAction(this);
+
+        // Make a copy to iterate over to avoid ConcurrentModificationException.
+        for (Persistent object : (ArrayList<Persistent>) new ArrayList(objects))
+            ocda.performDelete(object);
     }
 }
\ No newline at end of file

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java?rev=881740&r1=881739&r2=881740&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java Wed Nov 18 12:08:20 2009
@@ -308,27 +308,6 @@
     }
 
     /**
-     * Deletes an object locally, scheduling it for future deletion from the external data
-     * store.
-     */
-    @Override
-    public void deleteObject(Object object) {
-        new ObjectContextDeleteAction(this).performDelete((Persistent) object);
-    }
-
-    public void deleteObjects(Collection<?> objects) throws DeleteDenyException {
-        if (objects.isEmpty())
-            return;
-
-        // Don't call deleteObject() directly since it would be less efficient.
-        ObjectContextDeleteAction ocda = new ObjectContextDeleteAction(this);
-
-        // Make a copy to iterate over to avoid ConcurrentModificationException.
-        for (Persistent object : (ArrayList<Persistent>) new ArrayList(objects))
-            ocda.performDelete(object);
-    }
-
-    /**
      * Creates and registers a new Persistent object instance.
      */
     @Override
@@ -527,16 +506,6 @@
             object.setObjectId(id);
         }
 
-        // must follow this exact order of property initialization per CAY-653, i.e. have
-        // the id and the context in place BEFORE setPersistence is called
-        object.setObjectContext(this);
-        object.setPersistenceState(PersistenceState.NEW);
-        
-        synchronized (graphManager) {
-            graphManager.registerNode(id, object);
-            graphManager.nodeCreated(id);
-        }
-        
         injectInitialValue(object);
     }
 

Copied: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/DataChannelSyncCallbackAction.java (from r881253, cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataChannelSyncCallbackAction.java)
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/DataChannelSyncCallbackAction.java?p2=cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/DataChannelSyncCallbackAction.java&p1=cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataChannelSyncCallbackAction.java&r1=881253&r2=881740&rev=881740&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataChannelSyncCallbackAction.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/DataChannelSyncCallbackAction.java Wed Nov 18 12:08:20 2009
@@ -16,14 +16,13 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
-package org.apache.cayenne.access;
+package org.apache.cayenne;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Set;
 
-import org.apache.cayenne.DataChannel;
 import org.apache.cayenne.graph.GraphChangeHandler;
 import org.apache.cayenne.graph.GraphDiff;
 import org.apache.cayenne.graph.GraphManager;
@@ -32,10 +31,11 @@
 
 /**
  * @since 3.0
+ * note: made public in 3.1 to be used in all tiers
  */
-abstract class DataChannelSyncCallbackAction implements GraphChangeHandler {
+public abstract class DataChannelSyncCallbackAction implements GraphChangeHandler {
 
-    static DataChannelSyncCallbackAction getCallbackAction(
+    public static DataChannelSyncCallbackAction getCallbackAction(
             LifecycleCallbackRegistry callbackRegistry,
             GraphManager graphManager,
             GraphDiff changes,
@@ -73,9 +73,9 @@
 
     protected abstract boolean hasListeners();
 
-    abstract void applyPreCommit();
+    public abstract void applyPreCommit();
 
-    abstract void applyPostCommit();
+    public abstract void applyPostCommit();
 
     void apply(LifecycleEvent callbackType, Collection<?> objects) {
         if (seenIds != null && objects != null) {
@@ -166,13 +166,13 @@
         }
 
         @Override
-        void applyPreCommit() {
+        public void applyPreCommit() {
             apply(LifecycleEvent.PRE_PERSIST, persisted);
             apply(LifecycleEvent.PRE_UPDATE, updated);
         }
 
         @Override
-        void applyPostCommit() {
+        public void applyPostCommit() {
             apply(LifecycleEvent.POST_UPDATE, updated);
             apply(LifecycleEvent.POST_REMOVE, removed);
             apply(LifecycleEvent.POST_PERSIST, persisted);
@@ -192,12 +192,12 @@
         }
 
         @Override
-        void applyPreCommit() {
+        public void applyPreCommit() {
             // noop
         }
 
         @Override
-        void applyPostCommit() {
+        public void applyPostCommit() {
             apply(LifecycleEvent.POST_LOAD, updated);
             apply(LifecycleEvent.POST_LOAD, removed);
         }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ObjectContextDeleteAction.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ObjectContextDeleteAction.java?rev=881740&r1=881739&r2=881740&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ObjectContextDeleteAction.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ObjectContextDeleteAction.java Wed Nov 18 12:08:20 2009
@@ -23,14 +23,14 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.Map;
 
 import org.apache.cayenne.map.DeleteRule;
-import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.LifecycleEvent;
 import org.apache.cayenne.map.ObjRelationship;
 import org.apache.cayenne.reflect.ArcProperty;
 import org.apache.cayenne.reflect.AttributeProperty;
 import org.apache.cayenne.reflect.ClassDescriptor;
-import org.apache.cayenne.reflect.Property;
 import org.apache.cayenne.reflect.PropertyVisitor;
 import org.apache.cayenne.reflect.ToManyProperty;
 import org.apache.cayenne.reflect.ToOneProperty;
@@ -54,6 +54,10 @@
 
         if (oldState == PersistenceState.TRANSIENT
                 || oldState == PersistenceState.DELETED) {
+            // Drop out... especially in case of DELETED we might be about to get
+            // into a horrible recursive loop due to CASCADE delete rules.
+            // Assume that everything must have been done correctly already
+            // and *don't* do it again
             return false;
         }
 
@@ -69,6 +73,11 @@
                             + ", context: "
                             + context);
         }
+        
+        // must resolve HOLLOW objects before delete... needed
+        // to process relationships and optimistic locking...
+
+        context.prepareForAccess(object, null, false);
 
         if (oldState == PersistenceState.NEW) {
             deleteNew(object);
@@ -80,133 +89,148 @@
         return true;
     }
 
-    private void deleteNew(Persistent object) {
+    private void deleteNew(Persistent object) throws DeleteDenyException {
         object.setPersistenceState(PersistenceState.TRANSIENT);
         processDeleteRules(object, PersistenceState.NEW);
+        
+        // if an object was NEW, we must throw it out
         context.getGraphManager().unregisterNode(object.getObjectId());
     }
 
-    private void deletePersistent(Persistent object) {
+    private void deletePersistent(Persistent object) throws DeleteDenyException {
+        context.getEntityResolver().getCallbackRegistry().performCallbacks(
+                LifecycleEvent.PRE_REMOVE,
+                object);
+        
         int oldState = object.getPersistenceState();
         object.setPersistenceState(PersistenceState.DELETED);
         processDeleteRules(object, oldState);
         context.getGraphManager().nodeRemoved(object.getObjectId());
     }
 
-    private void processDeleteRules(final Persistent object, final int oldState) {
+    private Collection toCollection(Object object) {
 
-        String entityName = object.getObjectId().getEntityName();
-        final ObjEntity entity = context.getEntityResolver().getObjEntity(entityName);
-        ClassDescriptor descriptor = context.getEntityResolver().getClassDescriptor(
-                entityName);
+        if (object == null) {
+            return Collections.EMPTY_LIST;
+        }
 
-        descriptor.visitProperties(new PropertyVisitor() {
+        // create copies of collections to avoid iterator exceptions
+        if (object instanceof Collection) {
+            return new ArrayList((Collection) object);
+        }
+        else if (object instanceof Map) {
+            return new ArrayList(((Map) object).values());
+        }
+        else {
+            return Collections.singleton(object);
+        }
+    }
 
-            public boolean visitToMany(ToManyProperty property) {
-                ObjRelationship relationship = (ObjRelationship) entity
-                        .getRelationship(property.getName());
+    private void processDeleteRules(final Persistent object, int oldState)
+            throws DeleteDenyException {
 
-                processRules(object, property, relationship.getDeleteRule(), oldState);
-                return true;
-            }
+        ClassDescriptor descriptor = context.getEntityResolver().getClassDescriptor(
+                object.getObjectId().getEntityName());
 
-            public boolean visitToOne(ToOneProperty property) {
-                ObjRelationship relationship = (ObjRelationship) entity
-                        .getRelationship(property.getName());
+        for (final ObjRelationship relationship : descriptor.getEntity().getRelationships()) {
 
-                processRules(object, property, relationship.getDeleteRule(), oldState);
-                return true;
-            }
+            boolean processFlattened = relationship.isFlattened()
+                    && relationship.isToDependentEntity()
+                    && !relationship.isReadOnly();
 
-            public boolean visitAttribute(AttributeProperty property) {
-                return true;
+            // first check for no action... bail out if no flattened processing is needed
+            if (relationship.getDeleteRule() == DeleteRule.NO_ACTION && !processFlattened) {
+                continue;
             }
-        });
-    }
-
-    private void processRules(
-            Persistent object,
-            ArcProperty property,
-            int deleteRule,
-            int oldState) {
-
-        if (deleteRule == DeleteRule.NO_ACTION) {
-            return;
-        }
-
-        Collection<?> relatedObjects = relatedObjects(object, property);
-        if (relatedObjects.isEmpty()) {
-            return;
-        }
 
-        switch (deleteRule) {
+            ArcProperty property = (ArcProperty) descriptor.getProperty(relationship
+                    .getName());
+            Collection relatedObjects = toCollection(property.readProperty(object));
+
+            // no related object, bail out
+            if (relatedObjects.size() == 0) {
+                continue;
+            }
 
-            case DeleteRule.DENY:
+            // process DENY rule first...
+            if (relationship.getDeleteRule() == DeleteRule.DENY) {
                 object.setPersistenceState(oldState);
+
                 String message = relatedObjects.size() == 1
                         ? "1 related object"
                         : relatedObjects.size() + " related objects";
-                throw new DeleteDenyException(object, property.getName(), message);
-
-            case DeleteRule.NULLIFY:
-                ArcProperty reverseArc = property.getComplimentaryReverseArc();
+                throw new DeleteDenyException(object, relationship.getName(), message);
+            }
 
-                if (reverseArc != null) {
+            // process flattened with dependent join tables...
+            // joins must be removed even if they are non-existent or ignored in the
+            // object graph
+            if (processFlattened) {
+                Iterator iterator = relatedObjects.iterator();
+                while (iterator.hasNext()) {
+                    Persistent relatedObject = (Persistent) iterator.next();
+                    context.getGraphManager().arcDeleted(object.getObjectId(), relatedObject
+                            .getObjectId(), relationship.getName());
+                }
+            }
 
-                    if (reverseArc instanceof ToManyProperty) {
-                        for (Object relatedObject : relatedObjects) {
-                            ((ToManyProperty) reverseArc).removeTarget(
-                                    relatedObject,
-                                    object,
-                                    true);
-                        }
+            // process remaining rules
+            switch (relationship.getDeleteRule()) {
+                case DeleteRule.NO_ACTION:
+                    break;
+                case DeleteRule.NULLIFY:
+                    ArcProperty reverseArc = property.getComplimentaryReverseArc();
+
+                    if (reverseArc == null) {
+                        // nothing we can do here
+                        break;
                     }
-                    else {
-                        for (Object relatedObject : relatedObjects) {
-                            ((ToOneProperty) reverseArc).setTarget(
-                                    relatedObject,
-                                    null,
-                                    true);
-                        }
-                    }
-                }
 
-                break;
-            case DeleteRule.CASCADE:
+                    final Collection finalRelatedObjects = relatedObjects;
 
-                Iterator<?> iterator = relatedObjects.iterator();
-                while (iterator.hasNext()) {
-                    Persistent relatedObject = (Persistent) iterator.next();
+                    reverseArc.visit(new PropertyVisitor() {
 
-                    // this action object is stateless, so we can use 'performDelete'
-                    // recursively.
-                    performDelete(relatedObject);
-                }
+                        public boolean visitAttribute(AttributeProperty property) {
+                            return false;
+                        }
 
-                break;
-            default:
-                object.setPersistenceState(oldState);
-                throw new CayenneRuntimeException("Invalid delete rule: " + deleteRule);
-        }
-    }
+                        public boolean visitToMany(ToManyProperty property) {
+                            Iterator iterator = finalRelatedObjects.iterator();
+                            while (iterator.hasNext()) {
+                                Object relatedObject = iterator.next();
+                                property.removeTarget(relatedObject, object, true);
+                            }
 
-    private Collection<?> relatedObjects(Object object, Property property) {
-        Object related = property.readProperty(object);
+                            return false;
+                        }
 
-        if (related == null) {
-            return Collections.EMPTY_LIST;
-        }
-        // return collections by copy, to allow removal of objects from the underlying
-        // relationship inside the iterator
-        else if (property instanceof ToManyProperty) {
-            Collection<?> relatedCollection = (Collection<?>) related;
-            return relatedCollection.isEmpty()
-                    ? Collections.EMPTY_LIST
-                    : new ArrayList<Object>(relatedCollection);
-        }
-        // TODO: andrus 11/21/2007 - ToManyMapProperty check
-        else {
-            return Collections.singleton(related);
+                        public boolean visitToOne(ToOneProperty property) {
+                            // Inverse is to-one - find all related objects and
+                            // nullify the reverse relationship
+                            Iterator iterator = finalRelatedObjects.iterator();
+                            while (iterator.hasNext()) {
+                                Object relatedObject = iterator.next();
+                                property.setTarget(relatedObject, null, true);
+                            }
+                            return false;
+                        }
+                    });
+
+                    break;
+                case DeleteRule.CASCADE:
+                    // Delete all related objects
+                    Iterator iterator = relatedObjects.iterator();
+                    while (iterator.hasNext()) {
+                        Persistent relatedObject = (Persistent) iterator.next();
+                        performDelete(relatedObject);
+                    }
+
+                    break;
+                default:
+                    object.setPersistenceState(oldState);
+                    throw new CayenneRuntimeException("Invalid delete rule "
+                            + relationship.getDeleteRule());
+            }
         }
     }
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java?rev=881740&r1=881739&r2=881740&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java Wed Nov 18 12:08:20 2009
@@ -35,7 +35,6 @@
 import org.apache.cayenne.DataChannel;
 import org.apache.cayenne.DataObject;
 import org.apache.cayenne.DataRow;
-import org.apache.cayenne.DeleteDenyException;
 import org.apache.cayenne.Fault;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.ObjectId;
@@ -54,7 +53,6 @@
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.LifecycleEvent;
 import org.apache.cayenne.map.ObjAttribute;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.ObjRelationship;
@@ -629,18 +627,9 @@
         // note that the order of initialization of persistence artifacts below is
         // important - do not change it lightly
         object.setObjectId(id);
-        object.setObjectContext(this);
-        object.setPersistenceState(PersistenceState.NEW);
-        getObjectStore().registerNode(id, object);
-        getObjectStore().nodeCreated(id);
         
         injectInitialValue(object);
 
-        // invoke callbacks
-        getEntityResolver().getCallbackRegistry().performCallbacks(
-                LifecycleEvent.POST_ADD,
-                object);
-
         return object;
     }
 
@@ -684,21 +673,17 @@
         else {
             persistent.setObjectId(new ObjectId(entity.getName()));
         }
-
-        persistent.setObjectContext(this);
-        persistent.setPersistenceState(PersistenceState.NEW);
-
-        getObjectStore().registerNode(persistent.getObjectId(), object);
-        getObjectStore().nodeCreated(persistent.getObjectId());
-
-        // now we need to find all arc changes, inject missing value holders and pull in
-        // all transient connected objects
-
+        
         ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(
                 entity.getName());
         if (descriptor == null) {
             throw new IllegalArgumentException("Invalid entity name: " + entity.getName());
         }
+        
+        injectInitialValue(object);
+
+        // now we need to find all arc changes, inject missing value holders and pull in
+        // all transient connected objects
 
         descriptor.visitProperties(new PropertyVisitor() {
 
@@ -752,13 +737,6 @@
                 return true;
             }
         });
-        
-        injectInitialValue(object);
-
-        // invoke callbacks
-        getEntityResolver().getCallbackRegistry().performCallbacks(
-                LifecycleEvent.POST_ADD,
-                persistent);
     }
 
     /**
@@ -773,48 +751,6 @@
     }
 
     /**
-     * Schedules all objects in the collection for deletion on the next commit of this
-     * DataContext. Object's persistence state is changed to PersistenceState.DELETED;
-     * objects related to this object are processed according to delete rules, i.e.
-     * relationships can be unset ("nullify" rule), deletion operation is cascaded
-     * (cascade rule).
-     * <p>
-     * <i>"Nullify" delete rule side effect: </i> passing a collection representing
-     * to-many relationship with nullify delete rule may result in objects being removed
-     * from collection.
-     * </p>
-     * 
-     * @since 1.2
-     */
-    public void deleteObjects(Collection objects) {
-        if (objects.isEmpty()) {
-            return;
-        }
-
-        // clone object list... this maybe a relationship collection with nullify delete
-        // rule, so modifying
-        for (Persistent object : new ArrayList<Persistent>(objects)) {
-            deleteObject(object);
-        }
-    }
-
-    /**
-     * Schedules an object for deletion on the next commit of this DataContext. Object's
-     * persistence state is changed to PersistenceState.DELETED; objects related to this
-     * object are processed according to delete rules, i.e. relationships can be unset
-     * ("nullify" rule), deletion operation is cascaded (cascade rule).
-     * 
-     * @param object a persistent object that we want to delete.
-     * @throws DeleteDenyException if a DENY delete rule is applicable for object
-     *             deletion.
-     * @throws NullPointerException if object is null.
-     */
-    @Override
-    public void deleteObject(Object object) throws DeleteDenyException {
-        new DataContextDeleteAction(this).performDelete((Persistent) object);
-    }
-
-    /**
      * If the parent channel is a DataContext, reverts local changes to make this context
      * look like the parent, if the parent channel is a DataDomain, reverts all changes.
      * 

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataDomain.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataDomain.java?rev=881740&r1=881739&r2=881740&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataDomain.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataDomain.java Wed Nov 18 12:08:20 2009
@@ -28,6 +28,7 @@
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.DataChannel;
+import org.apache.cayenne.DataChannelSyncCallbackAction;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.QueryResponse;
 import org.apache.cayenne.access.jdbc.BatchQueryBuilderFactory;

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackRegistry.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackRegistry.java?rev=881740&r1=881739&r2=881740&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackRegistry.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackRegistry.java Wed Nov 18 12:08:20 2009
@@ -99,6 +99,7 @@
      */
     public void addListener(Class<?> entityClass, LifecycleListener listener) {
         addListener(LifecycleEvent.POST_ADD, entityClass, listener, "postAdd");
+        addListener(LifecycleEvent.POST_PERSIST, entityClass, listener, "prePersist");
         addListener(LifecycleEvent.POST_PERSIST, entityClass, listener, "postPersist");
         addListener(LifecycleEvent.PRE_REMOVE, entityClass, listener, "preRemove");
         addListener(LifecycleEvent.POST_REMOVE, entityClass, listener, "postRemove");

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/remote/ClientChannel.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/remote/ClientChannel.java?rev=881740&r1=881739&r2=881740&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/remote/ClientChannel.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/remote/ClientChannel.java Wed Nov 18 12:08:20 2009
@@ -24,6 +24,7 @@
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.DataChannel;
+import org.apache.cayenne.DataChannelSyncCallbackAction;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.Persistent;
@@ -169,6 +170,14 @@
             ObjectContext originatingContext,
             GraphDiff changes,
             int syncType) {
+        
+        DataChannelSyncCallbackAction callbackAction = DataChannelSyncCallbackAction
+            .getCallbackAction(
+                getEntityResolver().getCallbackRegistry(),
+                originatingContext.getGraphManager(),
+                changes,
+                syncType);
+        callbackAction.applyPreCommit();
 
         changes = diffCompressor.compress(changes);
 
@@ -221,6 +230,7 @@
             }
         }
 
+        callbackAction.applyPostCommit();
         return replyDiff;
     }
 

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/RemoteCallbacksTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/RemoteCallbacksTest.java?rev=881740&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/RemoteCallbacksTest.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/RemoteCallbacksTest.java Wed Nov 18 12:08:20 2009
@@ -0,0 +1,114 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.remote;
+
+import org.apache.cayenne.LifecycleListener;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.testdo.mt.ClientMtLifecycles;
+
+public class RemoteCallbacksTest extends RemoteCayenneCase implements LifecycleListener {
+    private int added, loaded, prePersisted, postPersisted, preRemoved, postRemoved, preUpdated, postUpdated;
+    
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        added = 0;
+        loaded = 0;
+        prePersisted = 0;
+        postPersisted = 0;
+        preRemoved = 0;
+        postRemoved = 0;
+        preUpdated = 0;
+        postUpdated = 0;
+    }
+    
+    public void testDefault() throws InterruptedException {
+        ObjectContext context = createROPContext();
+        context.getEntityResolver().getCallbackRegistry().addListener(ClientMtLifecycles.class, this);
+        
+        assertAll(0, 0, 0, 0, 0, 0, 0, 0);
+        ClientMtLifecycles l1 = context.newObject(ClientMtLifecycles.class);
+        
+        assertAll(1, 0, 0, 0, 0, 0, 0, 0);
+        l1.setName("x");
+        assertAll(1, 0, 0, 0, 0, 0, 0, 0);
+        
+        context.commitChanges();
+        Thread.sleep(5); //until commit
+        assertAll(1, 0, 1, 1, 0, 0, 0, 0);
+        
+        l1.setName("x2");
+        assertAll(1, 0, 1, 1, 0, 0, 0, 0);
+        
+        context.commitChanges();
+        Thread.sleep(5); //until commit
+        assertAll(1, 0, 1, 1, 1, 1, 0, 0);
+        
+        context.deleteObject(l1);
+        assertAll(1, 0, 1, 1, 1, 1, 1, 0);
+        
+        context.commitChanges();
+        Thread.sleep(5); //until commit
+        assertAll(1, 0, 1, 1, 1, 1, 1, 1);
+    }
+    
+    private void assertAll(int added, int loaded, int prePersisted, int postPersisted,
+            int preUpdated, int postUpdated, int preRemoved, int postRemoved) {
+        assertEquals(this.added, added);
+        assertEquals(this.loaded, loaded);
+        assertEquals(this.prePersisted, prePersisted);
+        assertEquals(this.postPersisted, postPersisted);
+        assertEquals(this.preRemoved, preRemoved);
+        assertEquals(this.postRemoved, postRemoved);
+        assertEquals(this.preUpdated, preUpdated);
+        assertEquals(this.postUpdated, postUpdated);
+    }
+
+    public void postAdd(Object entity) {
+        added++;
+    }
+
+    public void postLoad(Object entity) {
+        loaded++;
+    }
+
+    public void postPersist(Object entity) {
+        postPersisted++;
+    }
+
+    public void postRemove(Object entity) {
+        postRemoved++;
+    }
+
+    public void postUpdate(Object entity) {
+        postUpdated++;
+    }
+
+    public void prePersist(Object entity) {
+        prePersisted++;
+    }
+
+    public void preRemove(Object entity) {
+        preRemoved++;
+    }
+
+    public void preUpdate(Object entity) {
+        preUpdated++;
+    }
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/ClientMtLifecycles.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/ClientMtLifecycles.java?rev=881740&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/ClientMtLifecycles.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/ClientMtLifecycles.java Wed Nov 18 12:08:20 2009
@@ -0,0 +1,10 @@
+package org.apache.cayenne.testdo.mt;
+
+import org.apache.cayenne.testdo.mt.auto._ClientMtLifecycles;
+
+/**
+ * A persistent class mapped as "MtLifecycles" Cayenne entity.
+ */
+public class ClientMtLifecycles extends _ClientMtLifecycles {
+
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/MtLifecycles.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/MtLifecycles.java?rev=881740&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/MtLifecycles.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/MtLifecycles.java Wed Nov 18 12:08:20 2009
@@ -0,0 +1,7 @@
+package org.apache.cayenne.testdo.mt;
+
+import org.apache.cayenne.testdo.mt.auto._MtLifecycles;
+
+public class MtLifecycles extends _MtLifecycles {
+
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMtLifecycles.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMtLifecycles.java?rev=881740&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMtLifecycles.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMtLifecycles.java Wed Nov 18 12:08:20 2009
@@ -0,0 +1,37 @@
+package org.apache.cayenne.testdo.mt.auto;
+
+import org.apache.cayenne.PersistentObject;
+
+/**
+ * A generated persistent class mapped as "MtLifecycles" Cayenne entity. It is a good idea to
+ * avoid changing this class manually, since it will be overwritten next time code is
+ * regenerated. If you need to make any customizations, put them in a subclass.
+ */
+public abstract class _ClientMtLifecycles extends PersistentObject {
+
+    public static final String NAME_PROPERTY = "name";
+
+    protected String name;
+
+    public String getName() {
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "name", false);
+        }
+
+        return name;
+    }
+    public void setName(String name) {
+        if(objectContext != null) {
+            objectContext.prepareForAccess(this, "name", false);
+        }
+
+        Object oldValue = this.name;
+        this.name = name;
+
+        // notify objectContext about simple property change
+        if(objectContext != null) {
+            objectContext.propertyChanged(this, "name", oldValue, name);
+        }
+    }
+
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMultiTier.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMultiTier.java?rev=881740&r1=881739&r2=881740&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMultiTier.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_ClientMultiTier.java Wed Nov 18 12:08:20 2009
@@ -5,7 +5,7 @@
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.PersistentObject;
 import org.apache.cayenne.query.NamedQuery;
-import org.apache.cayenne.testdo.mt.ClientMtTable1;
+import org.apache.cayenne.testdo.mt.MtTable1;
 
 /**
  * This class was generated by Cayenne.
@@ -15,15 +15,21 @@
  */
 public class _ClientMultiTier {
 
-    public List<ClientMtTable1> performAllMtTable1(ObjectContext context ) {
+    public static final String ALL_MT_TABLE1_QUERYNAME = "AllMtTable1";
+
+    public static final String MT_QUERY_WITH_LOCAL_CACHE_QUERYNAME = "MtQueryWithLocalCache";
+
+    public static final String PARAMETERIZED_MT_QUERY_WITH_LOCAL_CACHE_QUERYNAME = "ParameterizedMtQueryWithLocalCache";
+
+    public List<MtTable1> performAllMtTable1(ObjectContext context ) {
         return context.performQuery(new NamedQuery("AllMtTable1"));
     }
 
-    public List<ClientMtTable1> performMtQueryWithLocalCache(ObjectContext context ) {
+    public List<MtTable1> performMtQueryWithLocalCache(ObjectContext context ) {
         return context.performQuery(new NamedQuery("MtQueryWithLocalCache"));
     }
 
-    public List<ClientMtTable1> performParameterizedMtQueryWithLocalCache(ObjectContext context , String g) {
+    public List<MtTable1> performParameterizedMtQueryWithLocalCache(ObjectContext context , String g) {
         String[] parameters = {
             "g",
         };

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MtLifecycles.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MtLifecycles.java?rev=881740&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MtLifecycles.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MtLifecycles.java Wed Nov 18 12:08:20 2009
@@ -0,0 +1,23 @@
+package org.apache.cayenne.testdo.mt.auto;
+
+import org.apache.cayenne.CayenneDataObject;
+
+/**
+ * Class _MtLifecycles was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _MtLifecycles extends CayenneDataObject {
+
+    public static final String NAME_PROPERTY = "name";
+
+
+    public void setName(String name) {
+        writeProperty("name", name);
+    }
+    public String getName() {
+        return (String)readProperty("name");
+    }
+
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MultiTier.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MultiTier.java?rev=881740&r1=881739&r2=881740&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MultiTier.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/mt/auto/_MultiTier.java Wed Nov 18 12:08:20 2009
@@ -14,6 +14,12 @@
  */
 public class _MultiTier {
 
+    public static final String ALL_MT_TABLE1_QUERYNAME = "AllMtTable1";
+
+    public static final String MT_QUERY_WITH_LOCAL_CACHE_QUERYNAME = "MtQueryWithLocalCache";
+
+    public static final String PARAMETERIZED_MT_QUERY_WITH_LOCAL_CACHE_QUERYNAME = "ParameterizedMtQueryWithLocalCache";
+
     public List<MtTable1> performAllMtTable1(ObjectContext context ) {
         return context.performQuery(new NamedQuery("AllMtTable1"));
     }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/multi-tier.map.xml
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/multi-tier.map.xml?rev=881740&r1=881739&r2=881740&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/multi-tier.map.xml (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/multi-tier.map.xml Wed Nov 18 12:08:20 2009
@@ -29,6 +29,10 @@
 		<db-attribute name="TABLE4_ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
 		<db-attribute name="TABLE5_ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
 	</db-entity>
+	<db-entity name="MT_LIFECYCLES">
+		<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+		<db-attribute name="NAME" type="VARCHAR" isMandatory="true" length="100"/>
+	</db-entity>
 	<db-entity name="MT_MAP_TO_MANY">
 		<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
 	</db-entity>
@@ -91,6 +95,9 @@
 	<obj-entity name="MtDeleteRule" className="org.apache.cayenne.testdo.mt.MtDeleteRule" clientClassName="org.apache.cayenne.testdo.mt.ClientMtDeleteRule" dbEntityName="MT_DELETE_RULE">
 		<obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/>
 	</obj-entity>
+	<obj-entity name="MtLifecycles" className="org.apache.cayenne.testdo.mt.MtLifecycles" clientClassName="org.apache.cayenne.testdo.mt.ClientMtLifecycles" dbEntityName="MT_LIFECYCLES">
+		<obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/>
+	</obj-entity>
 	<obj-entity name="MtMapToMany" className="org.apache.cayenne.testdo.mt.MtMapToMany" clientClassName="org.apache.cayenne.testdo.mt.ClientMtMapToMany" dbEntityName="MT_MAP_TO_MANY">
 	</obj-entity>
 	<obj-entity name="MtMapToManyTarget" className="org.apache.cayenne.testdo.mt.MtMapToManyTarget" clientClassName="org.apache.cayenne.testdo.mt.ClientMtMapToManyTarget" dbEntityName="MT_MAP_TO_MANY_TARGET">