diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java
index 42083b0374a784fe8cd66e55ecefd423d051e770..4409acacda1fda42366885fe21ee616617de1640 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/ETLService.java
@@ -49,8 +49,10 @@ import ch.systemsx.cisd.common.servlet.RequestContextProviderAdapter;
 import ch.systemsx.cisd.common.spring.HttpInvokerUtils;
 import ch.systemsx.cisd.common.spring.IInvocationLoggerContext;
 import ch.systemsx.cisd.openbis.generic.server.api.v1.SearchCriteriaToDetailedSearchCriteriaTranslator;
+import ch.systemsx.cisd.openbis.generic.server.batch.AbstractBatchOperationDelegate;
 import ch.systemsx.cisd.openbis.generic.server.batch.BatchOperationExecutor;
 import ch.systemsx.cisd.openbis.generic.server.batch.DataSetBatchUpdate;
+import ch.systemsx.cisd.openbis.generic.server.batch.IBatchOperationDelegate;
 import ch.systemsx.cisd.openbis.generic.server.batch.SampleUpdate;
 import ch.systemsx.cisd.openbis.generic.server.business.IDataStoreServiceFactory;
 import ch.systemsx.cisd.openbis.generic.server.business.IPropertiesBatchManager;
@@ -1632,7 +1634,7 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
         }
     }
 
-    private List<Sample> updateSamples(Session session,
+    private List<Sample> updateSamples(final Session session,
             AtomicEntityOperationDetails operationDetails, IProgressListener progress)
     {
         List<SampleUpdatesDTO> sampleUpdates = operationDetails.getSampleUpdates();
@@ -1655,10 +1657,23 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
         }
         assertInstanceSampleUpdateAllowed(session, instanceSamples);
         assertSpaceSampleUpdateAllowed(session, spaceSamples);
-        ISampleTable sampleTable = businessObjectFactory.createSampleTable(session);
-        BatchOperationExecutor.executeInBatches(new SampleUpdate(sampleTable, sampleUpdates),
-                progress, "updateSamples");
-        return SampleTranslator.translate(sampleTable.getSamples(), session.getBaseIndexURL());
+        final ISampleTable sampleTable = businessObjectFactory.createSampleTable(session);
+        final List<Sample> results = new ArrayList<Sample>();
+
+        IBatchOperationDelegate<SampleUpdatesDTO> delegate =
+                new AbstractBatchOperationDelegate<SampleUpdatesDTO>()
+                    {
+                        @Override
+                        public void batchOperationDidSave()
+                        {
+                            results.addAll(SampleTranslator.translate(sampleTable.getSamples(),
+                                    session.getBaseIndexURL()));
+                        }
+                    };
+
+        BatchOperationExecutor.executeInBatches(new SampleUpdate(sampleTable, sampleUpdates,
+                delegate), progress, "updateSamples");
+        return results;
     }
 
     private void assertInstanceSampleUpdateAllowed(Session session,
@@ -1710,7 +1725,7 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
         }
     }
 
-    private List<ExternalData> updateDataSets(Session session,
+    private List<ExternalData> updateDataSets(final Session session,
             AtomicEntityOperationDetails operationDetails, IProgressListener progress)
     {
         List<DataSetBatchUpdatesDTO> dataSetUpdates = operationDetails.getDataSetUpdates();
@@ -1719,22 +1734,27 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
             return Collections.emptyList();
         }
         assertDataSetUpdateAllowed(session, dataSetUpdates);
-        IDataSetTable dataSetTable = businessObjectFactory.createDataSetTable(session);
-        BatchOperationExecutor.executeInBatches(
-                new DataSetBatchUpdate(dataSetTable, dataSetUpdates), progress, "updateDataSets");
-        // These DataPEs are missing their relationships, so we need to retrieve these objects
-        // another way.
-        List<DataPE> updatedDataSets = dataSetTable.getDataSets();
-        List<String> updatedDataSetCodes = new ArrayList<String>();
-        for (DataPE data : updatedDataSets)
-        {
-            updatedDataSetCodes.add(data.getCode());
-        }
-        List<DataPE> dataSetsToReturn =
-                getDAOFactory().getDataDAO().tryToFindFullDataSetsByCodes(updatedDataSetCodes,
-                        true, false);
+        final IDataSetTable dataSetTable = businessObjectFactory.createDataSetTable(session);
+        final ArrayList<ExternalData> results = new ArrayList<ExternalData>();
+        IBatchOperationDelegate<DataSetBatchUpdatesDTO> delegate =
+                new AbstractBatchOperationDelegate<DataSetBatchUpdatesDTO>()
+                    {
+
+                        @Override
+                        public void batchOperationWillSave()
+                        {
+                            // Need to intercept before saving so we can translate the objects when
+                            // they still have Hibernate sessions.
+                            results.addAll(DataSetTranslator.translate(dataSetTable.getDataSets(),
+                                    "", session.getBaseIndexURL()));
+                        }
+
+                    };
+
+        BatchOperationExecutor.executeInBatches(new DataSetBatchUpdate(dataSetTable,
+                dataSetUpdates, delegate), progress, "updateDataSets");
 
-        return DataSetTranslator.translate(dataSetsToReturn, "", session.getBaseIndexURL());
+        return results;
     }
 
     private void assertDataSetUpdateAllowed(Session session, List<DataSetBatchUpdatesDTO> dataSets)
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/AbstractBatchOperation.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/AbstractBatchOperation.java
new file mode 100644
index 0000000000000000000000000000000000000000..cdf02e8538a4615d531764e8c7fd4c63f5d848ea
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/AbstractBatchOperation.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed 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 ch.systemsx.cisd.openbis.generic.server.batch;
+
+/**
+ * An abstract superclass for batch operations.
+ * 
+ * @author Chandrasekhar Ramakrishnan
+ */
+public abstract class AbstractBatchOperation<T> implements IBatchOperation<T>
+{
+    protected final IBatchOperationDelegate<T> delegateOrNull;
+
+    protected AbstractBatchOperation(IBatchOperationDelegate<T> delegateOrNull)
+    {
+        this.delegateOrNull = delegateOrNull;
+    }
+
+    /**
+     * Calls {@link IBatchOperationDelegate#batchOperationWillSave()} on the delegate.
+     */
+    protected void batchOperationWillSave()
+    {
+        if (null != delegateOrNull)
+        {
+            this.delegateOrNull.batchOperationWillSave();
+        }
+    }
+
+    /**
+     * Calls {@link IBatchOperationDelegate#batchOperationDidSave()} on the delegate.
+     */
+    protected void batchOperationDidSave()
+    {
+        if (null != delegateOrNull)
+        {
+            this.delegateOrNull.batchOperationDidSave();
+        }
+    }
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/AbstractBatchOperationDelegate.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/AbstractBatchOperationDelegate.java
new file mode 100644
index 0000000000000000000000000000000000000000..0f5f0193f8ed8e7436fd0adbb5df32446767154b
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/AbstractBatchOperationDelegate.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed 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 ch.systemsx.cisd.openbis.generic.server.batch;
+
+/**
+ * A shell superclass for batch operation delegates. Subclasses only need to override those methods
+ * they actually use.
+ * 
+ * @author Chandrasekhar Ramakrishnan
+ */
+public class AbstractBatchOperationDelegate<T> implements IBatchOperationDelegate<T>
+{
+
+    @Override
+    public void batchOperationWillSave()
+    {
+        // subclasses may override
+    }
+
+    @Override
+    public void batchOperationDidSave()
+    {
+        // subclasses may override
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/DataSetBatchUpdate.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/DataSetBatchUpdate.java
index 6ff5dc2edaf0efc983e6ea54e19ec11c103797f8..3e570de1cbdd93740a3e8c778e4322e6c8e12ab9 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/DataSetBatchUpdate.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/DataSetBatchUpdate.java
@@ -26,7 +26,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.DataSetBatchUpdatesDTO;
  * 
  * @author Izabela Adamczyk
  */
-public class DataSetBatchUpdate implements IBatchOperation<DataSetBatchUpdatesDTO>
+public class DataSetBatchUpdate extends AbstractBatchOperation<DataSetBatchUpdatesDTO>
 {
     private final IDataSetTable businessTable;
 
@@ -34,6 +34,13 @@ public class DataSetBatchUpdate implements IBatchOperation<DataSetBatchUpdatesDT
 
     public DataSetBatchUpdate(IDataSetTable businessTable, List<DataSetBatchUpdatesDTO> entities)
     {
+        this(businessTable, entities, null);
+    }
+
+    public DataSetBatchUpdate(IDataSetTable businessTable, List<DataSetBatchUpdatesDTO> entities,
+            IBatchOperationDelegate<DataSetBatchUpdatesDTO> delegate)
+    {
+        super(delegate);
         this.businessTable = businessTable;
         this.entities = entities;
     }
@@ -42,7 +49,10 @@ public class DataSetBatchUpdate implements IBatchOperation<DataSetBatchUpdatesDT
     public void execute(List<DataSetBatchUpdatesDTO> updates)
     {
         businessTable.update(updates);
+
+        batchOperationWillSave();
         businessTable.save();
+        batchOperationDidSave();
     }
 
     @Override
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/IBatchOperationDelegate.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/IBatchOperationDelegate.java
new file mode 100644
index 0000000000000000000000000000000000000000..6dfe0a8165cc8f259c3ffd7e2c6121a447963dca
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/IBatchOperationDelegate.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010 ETH Zuerich, CISD
+ *
+ * Licensed 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 ch.systemsx.cisd.openbis.generic.server.batch;
+
+/**
+ * A delegate that is notified of events as the batch operation progresses.
+ * 
+ * @author Chandrasekhar Ramakrishnan
+ */
+public interface IBatchOperationDelegate<T>
+{
+    /**
+     * Invoked just before the batch operation saves.
+     */
+    void batchOperationWillSave();
+
+    /**
+     * Invoked after the batch operation has saved.
+     */
+    void batchOperationDidSave();
+}
\ No newline at end of file
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/SampleUpdate.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/SampleUpdate.java
index 6c16b05bb432c1f29fe8c9cf1ffc4a3cb6f29165..eba771887b26d291aaf76effc05cc0cbc0fe7b82 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/SampleUpdate.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/batch/SampleUpdate.java
@@ -31,7 +31,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.SampleUpdatesDTO;
  * 
  * @author Chandrasekhar Ramakrishnan
  */
-public class SampleUpdate implements IBatchOperation<SampleUpdatesDTO>
+public class SampleUpdate extends AbstractBatchOperation<SampleUpdatesDTO>
 {
     private final ISampleTable businessTable;
 
@@ -39,6 +39,13 @@ public class SampleUpdate implements IBatchOperation<SampleUpdatesDTO>
 
     public SampleUpdate(ISampleTable businessTable, List<SampleUpdatesDTO> entities)
     {
+        this(businessTable, entities, null);
+    }
+
+    public SampleUpdate(ISampleTable businessTable, List<SampleUpdatesDTO> entities,
+            IBatchOperationDelegate<SampleUpdatesDTO> delegate)
+    {
+        super(delegate);
         this.businessTable = businessTable;
         this.entities = entities;
     }
@@ -47,7 +54,10 @@ public class SampleUpdate implements IBatchOperation<SampleUpdatesDTO>
     public void execute(List<SampleUpdatesDTO> updates)
     {
         businessTable.prepareForUpdateWithSampleUpdates(updates);
+
+        batchOperationWillSave();
         businessTable.save();
+        batchOperationDidSave();
     }
 
     @Override
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java
index cafb44a84be76d45e61510227a60b423e9bbe917..2180445ea8e48a70ebbec34d809749b9cd04c764 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java
@@ -684,7 +684,7 @@ public interface IETLLIMSService extends IServer, ISessionProvider, Conversation
     @Transactional
     @RolesAllowed(RoleWithHierarchy.SPACE_ETL_SERVER)
     @DatabaseUpdateModification(value =
-        { ObjectKind.SAMPLE, ObjectKind.EXPERIMENT })
+        { ObjectKind.SAMPLE, ObjectKind.EXPERIMENT, ObjectKind.DATA_SET })
     @DatabaseCreateOrDeleteModification(value =
         { ObjectKind.SPACE, ObjectKind.PROJECT, ObjectKind.SAMPLE, ObjectKind.EXPERIMENT,
                 ObjectKind.DATA_SET })
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java
index 168fc8b30e576ee52ffc7f4870da9a3bc13833cc..724f4819bd0b7333cbba180852a2191394d16304 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceTest.java
@@ -1164,10 +1164,6 @@ public class ETLServiceTest extends AbstractServerTestCase
                     one(dataSetTable).getDataSets();
                     final DataPE updatedDataSet = createDataSet(updatedDataSetCode, "type");
                     will(returnValue(Arrays.asList(updatedDataSet)));
-
-                    one(dataSetDAO).tryToFindFullDataSetsByCodes(Arrays.asList(updatedDataSetCode),
-                            true, false);
-                    will(returnValue(Arrays.asList(updatedDataSet)));
                 }
             });
     }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected
index 7874e6593ab545b4e6df29a627f5f742043d2c83..2180445ea8e48a70ebbec34d809749b9cd04c764 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected
@@ -684,7 +684,7 @@ public interface IETLLIMSService extends IServer, ISessionProvider, Conversation
     @Transactional
     @RolesAllowed(RoleWithHierarchy.SPACE_ETL_SERVER)
     @DatabaseUpdateModification(value =
-        { ObjectKind.SAMPLE, ObjectKind.EXPERIMENT })
+        { ObjectKind.SAMPLE, ObjectKind.EXPERIMENT, ObjectKind.DATA_SET })
     @DatabaseCreateOrDeleteModification(value =
         { ObjectKind.SPACE, ObjectKind.PROJECT, ObjectKind.SAMPLE, ObjectKind.EXPERIMENT,
                 ObjectKind.DATA_SET })
@@ -803,7 +803,7 @@ public interface IETLLIMSService extends IServer, ISessionProvider, Conversation
     @Transactional(readOnly = true)
     @RolesAllowed(RoleWithHierarchy.SPACE_ETL_SERVER)
     public EntityOperationsState didEntityOperationsSucceed(String token, TechId registrationId);
-    
+
     /**
      * Method that does nothing. Use it to check if the connection to the server is working.
      */
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/EntityOperationTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/EntityOperationTest.java
index dd35ffc1a6ecb106ae403b0781cba76bfa83bda8..0b7a52caedf381e7705efdbe67c332a9ca7b25ed 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/EntityOperationTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/systemtest/EntityOperationTest.java
@@ -21,6 +21,7 @@ import static org.testng.AssertJUnit.fail;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 
@@ -31,6 +32,7 @@ import ch.systemsx.cisd.common.exceptions.AuthorizationFailureException;
 import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSet;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetBatchUpdateDetails;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
@@ -226,7 +228,9 @@ public class EntityOperationTest extends SystemTestCase
         EntityOperationBuilder dataSetUpdate(ExternalData dataSet)
         {
             DataSetBatchUpdatesDTO dataSetUpdate = new DataSetBatchUpdatesDTO();
+            dataSetUpdate.setDetails(new DataSetBatchUpdateDetails());
             dataSetUpdate.setDatasetId(new TechId(dataSet));
+            dataSetUpdate.setDatasetCode(dataSet.getCode());
             dataSetUpdate.setVersion(dataSet.getModificationDate());
             if (dataSet instanceof DataSet)
             {
@@ -234,6 +238,15 @@ public class EntityOperationTest extends SystemTestCase
                 dataSetUpdate.setFileFormatTypeCode(realDataSet.getFileFormatType().getCode());
             }
             dataSetUpdate.setProperties(dataSet.getProperties());
+
+            // Request an update of all properties
+            HashSet<String> propertiesToUpdate = new HashSet<String>();
+            for (IEntityProperty property : dataSet.getProperties())
+            {
+                propertiesToUpdate.add(property.getPropertyType().getCode());
+            }
+            dataSetUpdate.getDetails().setPropertiesToUpdate(propertiesToUpdate);
+
             Sample sample = dataSet.getSample();
             if (sample != null)
             {