diff --git a/Database/APR/RootStorageSvc/src/RNTupleContainer.cpp b/Database/APR/RootStorageSvc/src/RNTupleContainer.cpp
index 169c2770c24e48fa9b7187e0bac82530adc634a5..9242d4450175afef5b52b37e68400f35a0f8fd95 100644
--- a/Database/APR/RootStorageSvc/src/RNTupleContainer.cpp
+++ b/Database/APR/RootStorageSvc/src/RNTupleContainer.cpp
@@ -24,6 +24,7 @@
 #include "RNTupleContainer.h"
 #include "RootDataPtr.h"
 #include "RootDatabase.h"
+#include "RNTupleWriterHelper.h"
 
 // Root include files
 #include "ROOT/RNTuple.hxx"
@@ -184,6 +185,14 @@ DbStatus RNTupleContainer::open( DbDatabase& dbH, const std::string& nam,
             log << DbPrintLvl::Debug << "Adding new RNTuple Field: name=" << dsc.fieldname 
                 << "  typename=" << dsc.typeName() << DbPrint::endmsg;
             m_ntupleWriter->addField( dsc.fieldname, dsc.typeName() );
+            if( dsc.hasAuxStore() ) {
+               dsc.auxdyn_writer = RootAuxDynIO::getNTupleAuxDynWriter();
+               if( !dsc.auxdyn_writer ) {
+                  log << DbPrintLvl::Error << "Cannot get AuxDyn writer for " << dsc.fieldname
+                      << DbPrint::endmsg;
+                  return Error;
+               }
+            }
          }
       }
       else if( mode & (pool::READ | pool::UPDATE) ) {
@@ -310,8 +319,11 @@ DbStatus RNTupleContainer::writeObject( ActionList::value_type& action )
        case DbColumn::POINTER:
           dsc.object            = p.ptr;
           try {
-             if( dsc.hasAuxStore() ) {
-                num_bytes += m_ntupleWriter->writeAuxAttributes( dsc.fieldname, dsc.getIOStorePtr(), dsc.rows_written );
+             if( dsc.auxdyn_writer ) {
+                auto attrList = dsc.auxdyn_writer->collectAuxAttributes( dsc.fieldname, dsc.getIOStorePtr() );
+                for(const auto& itr : attrList) {
+                   m_ntupleWriter->addAttribute( itr );
+                }
              }
           } catch(const std::exception& exc) {
              DbPrint err(m_name);
diff --git a/Database/APR/RootStorageSvc/src/RNTupleContainer.h b/Database/APR/RootStorageSvc/src/RNTupleContainer.h
index 519d47d31b03f08f708f334e5ff61f0cfbc35db0..ea136a0d607a8d6da112e61c1aa3415e4532b24e 100644
--- a/Database/APR/RootStorageSvc/src/RNTupleContainer.h
+++ b/Database/APR/RootStorageSvc/src/RNTupleContainer.h
@@ -22,7 +22,8 @@
 // Forward declarations
 class TClass;
 namespace SG { class IAuxStoreIO; }
-namespace RootAuxDynIO { class IRNTupleWriter; }
+namespace RootAuxDynIO { class IRootAuxDynReader; class IRNTupleAuxDynWriter; }
+namespace RootStorageSvc { class RNTupleWriterHelper; }
 namespace ROOT::Experimental { class RNTupleReader; }
 
 #include "ROOT/RNTupleView.hxx"
@@ -70,6 +71,9 @@ class RNTupleContainer : public DbContainerImp
     // AuxDyn RNTuple reader (managed by the Database)
     std::unique_ptr<RootAuxDynIO::IRootAuxDynReader> auxdyn_reader;
 
+    // AuxDyn RNTuple writer (managed by the Database)
+    std::unique_ptr<RootAuxDynIO::IRNTupleAuxDynWriter> auxdyn_writer;
+
     FieldDesc(const DbColumn& c);
     FieldDesc(FieldDesc const& other) = delete;
     FieldDesc(FieldDesc&& other) = default;
@@ -107,8 +111,10 @@ class RNTupleContainer : public DbContainerImp
    int64_t            m_indexBump;
    const uint32_t     m_indexMulti;
 
-   RootAuxDynIO::IRNTupleWriter*     m_ntupleWriter = nullptr;
-   /// Note: the Fields need to be destroyed before the page source is gone
+   /// Internal cache of the RNTupleWriterHelper
+   RootStorageSvc::RNTupleWriterHelper*     m_ntupleWriter = nullptr;
+
+   /// Internal cache of the native RNTupleReader
    RNTupleReader*       m_ntupleReader{};
 
  public:
diff --git a/Database/APR/RootStorageSvc/src/RNTupleWriterHelper.cpp b/Database/APR/RootStorageSvc/src/RNTupleWriterHelper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2842b1d32610d57566995a97cb22319ca2805240
--- /dev/null
+++ b/Database/APR/RootStorageSvc/src/RNTupleWriterHelper.cpp
@@ -0,0 +1,167 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "RNTupleWriterHelper.h"
+
+#include "ROOT/RNTupleModel.hxx"
+#include "RootUtils/APRDefaults.h"
+#include "TFile.h"
+
+namespace RootStorageSvc {
+
+RNTupleWriterHelper::RNTupleWriterHelper(TFile* file,
+                                         const std::string& ntupleName,
+                                         bool enableBufferedWrite,
+                                         bool enableMetrics)
+    : AthMessaging(std::string("RNTupleWriterHelper[") + ntupleName + "]"),
+      m_model(RNTupleModel::Create()),
+      m_ntupleName(ntupleName),
+      m_tfile(file),
+      m_collectMetrics(enableMetrics) {
+  m_opts.SetCompression(m_tfile->GetCompressionSettings());
+  m_opts.SetUseBufferedWrite(enableBufferedWrite);
+  m_model->SetDescription(ntupleName);
+  RNTupleWriterHelper::addField(APRDefaults::IndexColName, "std::uint64_t");
+}
+
+void RNTupleWriterHelper::makeNewEntry() {
+  if (m_model) {
+    // prepare for writing of the first row
+    if (!m_tfile) {
+      throw std::runtime_error(std::string("Attempt to write RNTuple ") +
+                               m_ntupleName + " without valid TFile ptr");
+    } else {
+      // write into existing file
+      ATH_MSG_DEBUG("Creating RNTuple " << m_tfile->GetName() << "/"
+                                        << m_ntupleName);
+      m_ntupleWriter = RNTupleWriter::Append(std::move(m_model), m_ntupleName,
+                                             *m_tfile, m_opts);
+      if (m_collectMetrics)
+        m_ntupleWriter->EnableMetrics();
+    }
+  }
+#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 31, 0)
+  m_entry = m_ntupleWriter->GetModel().CreateBareEntry();
+#else
+  m_entry = m_ntupleWriter->GetModel()->CreateBareEntry();
+#endif
+}
+
+void RNTupleWriterHelper::addAttribute(const attrDataTuple& in) {
+  const std::string& name = std::get<0>(in);
+  const std::string& type = std::get<1>(in);
+  void* data = std::get<2>(in);
+  ATH_MSG_DEBUG("Adding Dynamic Attribute " << name << " of type " << type);
+  if (m_attrDataMap.find(name) == m_attrDataMap.end()) {
+    addField(name, type);
+  }
+  addFieldValue(name, data);
+}
+
+/// Add a new field to the RNTuple
+/// Used for data objects from RNTupleContainer, not for dynamic attributes
+void RNTupleWriterHelper::addField(const std::string& field_name,
+                                   const std::string& attr_type) {
+  if (m_attrDataMap.find(field_name) != m_attrDataMap.end()) {
+    throw std::runtime_error(
+        std::string("Attempt to add existing field.  name: ") + field_name +
+        "new type: " + attr_type);
+  }
+  ATH_MSG_DEBUG("Adding new object column, name=" << field_name << " of type "
+                                                  << attr_type);
+  auto field = RFieldBase::Create(field_name, attr_type).Unwrap();
+  if (!m_model) {
+#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 31, 0)
+    // first write was already done, need to update the model
+    ATH_MSG_DEBUG("Adding late attribute " << field_name);
+    auto updater = m_ntupleWriter->CreateModelUpdater();
+    updater->BeginUpdate();
+    updater->AddField(std::move(field));
+    updater->CommitUpdate();
+#endif
+  } else {
+    m_model->AddField(std::move(field));
+  }
+  m_attrDataMap[field_name] = nullptr;
+}
+
+/// Supply data address for a given field
+void RNTupleWriterHelper::addFieldValue(const std::string& field_name,
+                                        void* attr_data) {
+  auto field_iter = m_attrDataMap.find(field_name);
+  if (field_iter == m_attrDataMap.end()) {
+    std::stringstream msg;
+    msg << "Attempt to write unknown Field with name: '" << field_name
+        << std::ends;
+    throw std::runtime_error(msg.str());
+  }
+  // already started writing
+  field_iter->second = attr_data;
+  m_needsCommit = true;
+}
+
+int RNTupleWriterHelper::commit() {
+#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 31, 0)
+  // write only if there was data added, ignore empty commits
+  if (!needsCommit()) {
+    ATH_MSG_DEBUG("Empty Commit");
+    return 0;
+  }
+  ATH_MSG_DEBUG("Commit, row=" << m_rowN << " : " << m_ntupleName);
+  if (!m_entry)
+    makeNewEntry();
+  // update index field before commit
+
+  int num_bytes = 0;
+  ATH_MSG_DEBUG(m_ntupleName << " has " << m_attrDataMap.size()
+                             << " attributes");
+  int attrN = 0;
+  for (auto& attr : m_attrDataMap) {
+    ATH_MSG_VERBOSE("Setting data ptr for field# "
+                    << ++attrN << ": " << attr.first << "  data=" << std::hex
+                    << attr.second << std::dec);
+    // If an object already exists bind it to the field
+    // Otherwise, create a new value and use its address
+    if (attr.second) {
+      m_entry->BindRawPtr(attr.first, attr.second);
+    } else {
+      m_entry->EmplaceNewValue(attr.first);
+      attr.second = m_entry->GetPtr<void>(attr.first).get();
+    }
+  }
+  num_bytes += m_ntupleWriter->Fill(*m_entry);
+  ATH_MSG_DEBUG("Filled RNTuple Row, bytes written: " << num_bytes);
+
+  m_entry.reset();
+  // forget all values to see if any object is missing in a new commit
+  for (auto& attr : m_attrDataMap) {
+    attr.second = nullptr;
+  }
+  m_needsCommit = false;
+  m_rowN++;
+
+  return num_bytes;
+#else
+  ATH_MSG_WARNING("Commit not implemented for this ROOT version");
+  return 0;
+#endif
+}
+
+void RNTupleWriterHelper::close() {
+  // Print metrics if enabled - this can become DEBUG/VERBOSE
+  if (m_ntupleWriter->GetMetrics().IsEnabled()) {
+    auto& log = msg(MSG::INFO);
+    log << "Printing I/O Statistics\n";
+    m_ntupleWriter->GetMetrics().Print(log.stream());
+    log << endmsg;
+  }
+  // delete the generated default fields (RField should delete the default data
+  // objects)
+  m_ntupleWriter.reset();
+  m_entry.reset();
+  m_model.reset();
+  m_rowN = 0;
+}
+
+}  // namespace RootStorageSvc
diff --git a/Database/APR/RootStorageSvc/src/RNTupleWriterHelper.h b/Database/APR/RootStorageSvc/src/RNTupleWriterHelper.h
new file mode 100644
index 0000000000000000000000000000000000000000..84a8644fd377a5aab8f436e8581cd9c10e98572d
--- /dev/null
+++ b/Database/APR/RootStorageSvc/src/RNTupleWriterHelper.h
@@ -0,0 +1,111 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef RNTUPLEWRITERHELPER_H
+#define RNTUPLEWRITERHELPER_H
+
+#include "AthenaBaseComps/AthMessaging.h"
+#include "ROOT/REntry.hxx"
+#include "ROOT/RField.hxx"
+#include "ROOT/RNTuple.hxx"
+#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 31, 0)
+#include "ROOT/RNTupleWriteOptions.hxx"
+#include "ROOT/RNTupleWriter.hxx"
+#else
+#include "ROOT/RNTupleOptions.hxx"
+#endif
+
+#include <tuple>
+
+namespace ROOT::Experimental {
+class RNTupleModel;
+}
+
+namespace RootStorageSvc {
+#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 31, 0)
+using RFieldBase = ROOT::Experimental::RFieldBase;
+#else
+using RFieldBase = ROOT::Experimental::Detail::RFieldBase;
+#endif
+using RNTupleWriter = ROOT::Experimental::RNTupleWriter;
+using RNTupleModel = ROOT::Experimental::RNTupleModel;
+using REntry = ROOT::Experimental::REntry;
+using RNTupleWriteOptions = ROOT::Experimental::RNTupleWriteOptions;
+
+class RNTupleWriterHelper : public AthMessaging {
+ public:
+  /// Constructor
+  RNTupleWriterHelper(TFile* file, const std::string& ntupleName,
+                      bool enableBufferedWrite, bool enableMetrics);
+
+  /// Default Destructor
+  ~RNTupleWriterHelper() = default;
+
+  /// Create a new empty RNTuple row with the current model (fields)
+  void makeNewEntry();
+
+  /// Add a new field to the RNTuple, collect the data pointer for the commit
+  // The convention for the tuple is <name, type, data>
+  typedef std::tuple<std::string, std::string, void*> attrDataTuple;
+  void addAttribute(const attrDataTuple& in);
+
+  /// Add a new field to the RNTuple
+  void addField(const std::string& field_name, const std::string& attr_type);
+
+  /// Supply data address for a given field
+  void addFieldValue(const std::string& field_name, void* attr_data);
+
+  /// Commit the data
+  int commit();
+
+  /// Name of the RNTuple
+  const std::string& getName() const { return m_ntupleName; }
+
+  /// Size of the RNTuple
+  size_t size() const { return m_rowN; }
+
+  /// Check if any data needs to be committed
+  bool needsCommit() const { return m_needsCommit; }
+
+  /// Is this RNTuple used by more than one APR container?
+  bool isGrouped() const { return m_clients > 1; }
+
+  /// Keep track of how many APR containers are writing to this RNTuple
+  void increaseClientCount() { m_clients++; }
+
+  /// Close the writer
+  void close();
+
+ private:
+  /// Store data ptr for the first row, when only creating the model
+  std::map<std::string, void*> m_attrDataMap;
+
+  /// Internal cache for the RNTuple model
+  /// Before first commit the fields are added to the model
+  /// At the first commit the model passed to the RNTupleWriter
+  /// which takes its ownership
+  std::unique_ptr<RNTupleModel> m_model;
+
+  /// Internal cache for the RNEntry
+  std::unique_ptr<REntry> m_entry;
+
+  /// Internal cache for the native RNTupleWriter
+  std::unique_ptr<RNTupleWriter> m_ntupleWriter;
+
+  std::string m_ntupleName;
+  TFile* m_tfile;
+  RNTupleWriteOptions m_opts;
+  int m_rowN = 0;
+
+  /// Count how many APR Containers are writing to this RNTuple (more than one
+  /// makes a Group)
+  int m_clients = 0;
+  bool m_needsCommit = false;
+
+  /// Enable/Disable Metric Collection
+  bool m_collectMetrics;
+};
+
+}  // namespace RootStorageSvc
+#endif
diff --git a/Database/APR/RootStorageSvc/src/RootDatabase.cpp b/Database/APR/RootStorageSvc/src/RootDatabase.cpp
index 13aba110ed3fd4202069fe3543ec9f6d634694fb..03f24ba80d8505f6d72ee5dd67d2e17cf171441f 100644
--- a/Database/APR/RootStorageSvc/src/RootDatabase.cpp
+++ b/Database/APR/RootStorageSvc/src/RootDatabase.cpp
@@ -18,6 +18,8 @@
 #include "POOLCore/DbPrint.h"
 #include "RootUtils/APRDefaults.h"
 #include "RootAuxDynIO/RootAuxDynIO.h"
+#include "RNTupleWriterHelper.h"
+#include "RootUtils/APRDefaults.h"
 
 #include "GaudiKernel/Bootstrap.h"
 #include "GaudiKernel/ConcurrencyFlags.h"
@@ -920,16 +922,16 @@ DbStatus RootDatabase::transAct(Transaction::Action action)
             if (cl != nullptr && cl->InheritsFrom("TTree")) {
                TTree* tree = static_cast<TTree*>(m_file->Get(key->GetName()));
                DbPrint log( m_file->GetName() );
-               if (tree != nullptr && tree->GetBranch("index_ref") != nullptr && tree->GetEntries() > 0) {
+               if (tree != nullptr && tree->GetBranch(APRDefaults::IndexColName) != nullptr && tree->GetEntries() > 0) {
                   TList* friendTrees(tree->GetListOfFriends());
                   if (friendTrees != nullptr && !friendTrees->IsEmpty()) {
-                     log << DbPrintLvl::Debug << "BuildIndex for index_ref to " << tree->GetName() << DbPrint::endmsg;
-                     tree->BuildIndex("index_ref");
+                     log << DbPrintLvl::Debug << "BuildIndex for " << APRDefaults::IndexColName << " to " << tree->GetName() << DbPrint::endmsg;
+                     tree->BuildIndex(APRDefaults::IndexColName);
                      for (const auto&& obj: *friendTrees) {
                         TTree* friendTree = tree->GetFriend(obj->GetName());
-                        if (friendTree != nullptr && friendTree->GetBranch("index_ref") != nullptr && friendTree->GetEntries() > 0) {
-                           log << DbPrintLvl::Debug << "BuildIndex for index_ref to " << friendTree->GetName() << DbPrint::endmsg;
-                           friendTree->BuildIndex("index_ref");
+                        if (friendTree != nullptr && friendTree->GetBranch(APRDefaults::IndexColName) != nullptr && friendTree->GetEntries() > 0) {
+                           log << DbPrintLvl::Debug << "BuildIndex for " << APRDefaults::IndexColName << " to " << friendTree->GetName() << DbPrint::endmsg;
+                           friendTree->BuildIndex(APRDefaults::IndexColName);
                         }
                      }
                   }
@@ -1118,7 +1120,7 @@ RootDatabase::getNTupleReader(const std::string& ntuple_name)
 }
 
 
-RootAuxDynIO::IRNTupleWriter*
+RootStorageSvc::RNTupleWriterHelper*
 RootDatabase::getNTupleWriter(const std::string& ntuple_name, bool create)
 {
    auto& writer = m_ntupleWriterMap[ntuple_name];
@@ -1127,7 +1129,7 @@ RootDatabase::getNTupleWriter(const std::string& ntuple_name, bool create)
          DbPrint log("RootDatabase.getNTupleWriter");
          log << DbPrintLvl::Warning << "Buffered writing doesn't work reliably in MT jobs yet, use at your own risk!" << DbPrint::endmsg;
       }
-      writer = RootAuxDynIO::getNTupleAuxDynWriter(m_file, ntuple_name, m_rntBufferedWriteEnabled, m_rntWriterMetricsEnabled);
+      writer = std::make_unique<RootStorageSvc::RNTupleWriterHelper>(m_file, ntuple_name, m_rntBufferedWriteEnabled, m_rntWriterMetricsEnabled);
    }
    if( writer and create ) {
       // treat the create flag as an indication of a new container client and count them
@@ -1147,7 +1149,7 @@ uint64_t RootDatabase::indexLookup([[maybe_unused]] RNTupleReader* reader, uint6
       indexLookup_t &index = m_ntupleIndexMap[reader];
       index.reserve(reader->GetNEntries());
       // This is the field in which the indices are kept
-      auto indexRefField = reader->GetView<uint64_t>("index_ref");
+      auto indexRefField = reader->GetView<uint64_t>(APRDefaults::IndexColName);
       // Loop over the events, read the index values and fill the map
       uint64_t row{0};
       for(const auto& entry : reader->GetEntryRange()) {
diff --git a/Database/APR/RootStorageSvc/src/RootDatabase.h b/Database/APR/RootStorageSvc/src/RootDatabase.h
index a90f2eadecf2f4aa7b9f3b80de8827c83ee0fd13..e21683e21728f2041f8e4a07512ec8e29f44924a 100644
--- a/Database/APR/RootStorageSvc/src/RootDatabase.h
+++ b/Database/APR/RootStorageSvc/src/RootDatabase.h
@@ -20,7 +20,6 @@
 #include <mutex>
 #include <unordered_map>
 
-
 // Forward declarations
 namespace ROOT { namespace Experimental {
    class RNTupleReader;
@@ -33,7 +32,9 @@ class TBranch;
 class IFileMgr;
 namespace RootAuxDynIO {
    class IRootAuxDynReader;
-   class IRNTupleWriter;
+}
+namespace RootStorageSvc {
+   class RNTupleWriterHelper;
 }
 
 /*
@@ -124,7 +125,7 @@ namespace pool  {
     // mutex to prevent concurrent read I/O from AuxDynReader
     std::recursive_mutex  m_iomutex;
 
-    std::map<std::string, std::unique_ptr<RootAuxDynIO::IRNTupleWriter> >  m_ntupleWriterMap;
+    std::map<std::string, std::unique_ptr<RootStorageSvc::RNTupleWriterHelper> >  m_ntupleWriterMap;
     std::map<std::string, std::unique_ptr<RNTupleReader> >                 m_ntupleReaderMap;
 
     using indexLookup_t = std::unordered_map<uint64_t, uint64_t>;
@@ -244,9 +245,9 @@ namespace pool  {
     // translate index value to row# for a given RNTuple  
     uint64_t            indexLookup(RNTupleReader *ps, uint64_t idx_val);
 
-    /// return NTupleWriter for a given ntuple_name
+    /// Return RNTupleWriterHelper for a given ntuple_name
     /// create a new one if needed when create==true
-    RootAuxDynIO::IRNTupleWriter*  getNTupleWriter(const std::string& ntuple_name, bool create=false);
+    RootStorageSvc::RNTupleWriterHelper*  getNTupleWriter(const std::string& ntuple_name, bool create=false);
 
   protected:
     // Execute any pending Fills before commit or flush
diff --git a/Database/AthenaRoot/RootAuxDynIO/RootAuxDynIO/RootAuxDynIO.h b/Database/AthenaRoot/RootAuxDynIO/RootAuxDynIO/RootAuxDynIO.h
index c2bbbabd29e3490451b6c5bc42229a33325ad7d6..9f646f863fcf854a50c20304a1dfbc5e1b63c16a 100644
--- a/Database/AthenaRoot/RootAuxDynIO/RootAuxDynIO/RootAuxDynIO.h
+++ b/Database/AthenaRoot/RootAuxDynIO/RootAuxDynIO/RootAuxDynIO.h
@@ -9,7 +9,9 @@
 
 #include <string>
 #include <memory>
+#include <vector>
 #include <mutex>
+#include <tuple>
 #include "RootAuxDynIO/RootAuxDynDefs.h"
 
 class TBranch;
@@ -17,7 +19,7 @@ class TTree;
 class TFile;
 class TClass;
 
-namespace ROOT { namespace Experimental {
+namespace ROOT::Experimental {
   class RNTupleReader;
 #if ROOT_VERSION_CODE < ROOT_VERSION( 6, 31, 0 )
   namespace Detail {
@@ -26,7 +28,7 @@ namespace ROOT { namespace Experimental {
 #else
   class RFieldBase;
 #endif
-} }
+}
 namespace SG { class IAuxStoreIO;  class auxid_set_t; }
 
 
@@ -40,6 +42,7 @@ namespace RootAuxDynIO
    using ROOT::Experimental::RNTupleReader;
    class IRootAuxDynReader;
    class IRootAuxDynWriter;
+   class IRNTupleAuxDynWriter;
    class IRNTupleWriter;
 
    /// check if a field/branch with fieldname and type tc has IAuxStore interface
@@ -61,9 +64,12 @@ namespace RootAuxDynIO
    std::unique_ptr<IRootAuxDynWriter> getBranchAuxDynWriter(TTree*, int bufferSize, int splitLevel,
                                                               int offsettab_len, bool do_branch_fill);
    
-   std::unique_ptr<IRootAuxDynReader> getNTupleAuxDynReader(const std::string& field_name, const std::string& field_type, RNTupleReader* reader);
-   std::unique_ptr<IRNTupleWriter>    getNTupleAuxDynWriter(TFile*,  const std::string& ntupleName, bool enableBufferedWrite, bool enableMetrics);
+   std::unique_ptr<IRootAuxDynReader>    getNTupleAuxDynReader(const std::string& field_name, const std::string& field_type, RNTupleReader* reader);
+   std::unique_ptr<IRNTupleAuxDynWriter> getNTupleAuxDynWriter();
+   std::unique_ptr<IRNTupleWriter>       getNTupleWriter(TFile*,  const std::string& ntupleName, bool enableBufferedWrite, bool enableMetrics);
 
+   // The convention for the tuple is <name, type, data>
+   typedef std::tuple<std::string, std::string, void*> attrDataTuple;
 
    class IRootAuxDynReader
    {
@@ -108,39 +114,15 @@ namespace RootAuxDynIO
       virtual void setBranchFillMode(bool) = 0;
    };
 
-   
-   /// Interface for a generic RNTuple-based Writer (can handle both normal objects and AuxDyn attributes
-   class IRNTupleWriter {
+   /// Interface for a RNTuple-based Writer that handles AuxDyn attributes 
+   /// Works in conjuction with the generic writer
+   class IRNTupleAuxDynWriter {
    public:
-      virtual ~IRNTupleWriter() {}
-
-      virtual const std::string& getName() const = 0;
-
-      virtual size_t size() const = 0;
-
-      /// Add a new field to the RNTuple
-      virtual void addField( const std::string& field_name, const std::string& attr_type ) = 0;
-
-      /// Supply data address for a given field
-      virtual void addFieldValue( const std::string& field_name, void* attr_data ) = 0;
-
-      /// handle writing of dynamic xAOD attributes of an AuxContainer - called from RNTupleContainer::writeObject()
-      /// should report bytes written  - it does not do than yet though
-      //  may throw exceptions
-      virtual int writeAuxAttributes(const std::string& base_branch, SG::IAuxStoreIO* store, size_t rows_written ) = 0;
-
-      /// Add a APR container to this RNTuple - if there is more than one than do grouped DB commit
-      virtual void increaseClientCount() = 0;
-      /// Check if there is more than one container writing to this RNTuple
-      virtual bool isGrouped() const = 0;
-      
-      /// is there a need to call commit()?
-      virtual bool needsCommit() const = 0;
-
-      /// Call Fill() on the ROOT object used by this writer
-      virtual int commit() = 0;
+      /// Default Destructor
+      virtual ~IRNTupleAuxDynWriter() = default;
 
-      virtual void close() = 0;
+      /// Collect Aux data information to be writting out
+      virtual std::vector<attrDataTuple> collectAuxAttributes( const std::string& base_branch, SG::IAuxStoreIO* store ) = 0;
    };
 
 } // namespace
diff --git a/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynReader.h b/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynReader.h
index 2afb0eabad618ce26dff680c9d8bcfdbb579580c..2874b3ac071d52f02891ff5e1d0cca2c06d330a9 100644
--- a/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynReader.h
+++ b/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynReader.h
@@ -14,7 +14,6 @@
 #include <map>
 #include <string>
 
-namespace RootAuxDynIO { class IRNTupleWriter; }
 namespace ROOT::Experimental {
    class RNTupleReader; 
 }
diff --git a/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.cxx b/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.cxx
index d0e37613be875c5b6ff276483e3c4903281d285c..adc0a40698a0fe134e7755ce837a409905f905c8 100644
--- a/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.cxx
+++ b/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.cxx
@@ -3,184 +3,34 @@
 */
 
 #include "RNTupleAuxDynWriter.h"
+
 #include "AthContainers/AuxStoreInternal.h"
 #include "AthContainers/AuxTypeRegistry.h"
 #include "AthContainers/normalizedTypeinfoName.h"
 
-#include "TFile.h"
-#include "Compression.h"
-#include "TClass.h"
-#include "TVirtualCollectionProxy.h"
-
-#include "ROOT/RNTupleModel.hxx"
-
-#include <sstream>
-#include <iostream>
-
-
-namespace RootAuxDynIO
-{
-
-   RNTupleAuxDynWriter::RNTupleAuxDynWriter(TFile* file, const std::string& ntupleName, bool enableBufferedWrite, bool enableMetrics) :
-         AthMessaging(std::string("RNTupleAuxDynWriter[")+ntupleName+"]"),
-         m_model( RNTupleModel::Create() ),
-         m_ntupleName( ntupleName ),
-         m_tfile( file ),
-         m_collectMetrics( enableMetrics )
-      {
-         m_opts.SetCompression( m_tfile->GetCompressionSettings() );
-         m_opts.SetUseBufferedWrite( enableBufferedWrite );
-         m_model->SetDescription( ntupleName );
-         RNTupleAuxDynWriter::addField("index_ref", "std::uint64_t");
-      }
-
-
-   void  RNTupleAuxDynWriter::makeNewEntry() {
-      if( m_model ) {
-         // prepare for writing of the first row
-         if( !m_tfile ) {
-            throw std::runtime_error( std::string("Attempt to write RNTuple ") + m_ntupleName + " without valid TFile ptr" );
-         } else {
-            // write into existing file
-            ATH_MSG_DEBUG("Creating RNTuple " << m_tfile->GetName() << "/" << m_ntupleName);
-            m_ntupleWriter = RNTupleWriter::Append(std::move(m_model), m_ntupleName, *m_tfile, m_opts);
-            if( m_collectMetrics ) m_ntupleWriter->EnableMetrics();
-         }
-      }
-#if ROOT_VERSION_CODE >= ROOT_VERSION( 6, 31, 0 )
-      m_entry = m_ntupleWriter->GetModel().CreateBareEntry();
-#else
-      m_entry = m_ntupleWriter->GetModel()->CreateBareEntry();
-#endif
-   }
-
-
-   /// handle writing of dynamic xAOD attributes of an object - called from RootTreeContainer::writeObject()
-   //  throws exceptions
-   int RNTupleAuxDynWriter::writeAuxAttributes( const std::string& base_name, SG::IAuxStoreIO* store, size_t /*rows_written*/ )
-   {
-      const SG::auxid_set_t selection = store->getSelectedAuxIDs();
-      ATH_MSG_DEBUG("Writing " << base_name << " with " << selection.size() << " Dynamic attributes");
-      for(SG::auxid_t id : selection) {
-         const std::string attr_type = SG::normalizedTypeinfoName( *store->getIOType(id) );
-         const std::string attr_name = SG::AuxTypeRegistry::instance().getName(id);
-         const std::string field_name = RootAuxDynIO::auxFieldName( attr_name, base_name );
-         void* attr_data ATLAS_THREAD_SAFE = const_cast<void*>( store->getIOData(id) );
-
-         addAttribute( field_name, attr_type, attr_data );
-      }
-      return 0;  // MN: can get bytes written only when calling Fill() at commit
-   }
-
-
-   void RNTupleAuxDynWriter::addAttribute( const std::string& field_name, const std::string& attr_type, void* attr_data )
-   {
-      if( m_attrDataMap.find(field_name) == m_attrDataMap.end() ) {
-         addField(field_name, attr_type);
-      }
-      addFieldValue(field_name, attr_data);
-   }
-
-   /// Add a new field to the RNTuple
-   /// Used for data objects from RNTupleContainer, not for dynamic attributes
-   void RNTupleAuxDynWriter::addField( const std::string& field_name, const std::string& attr_type )
-   {
-      if( m_attrDataMap.find(field_name) != m_attrDataMap.end() ) {
-         throw std::runtime_error( std::string("Attempt to add existing field.  name: ")
-                              + field_name + "new type: " + attr_type );
-      }
-      ATH_MSG_DEBUG("Adding new object column, name="<< field_name << " of type " << attr_type);
-      auto field = RFieldBase::Create(field_name, attr_type).Unwrap();
-      if( !m_model ) {
-#if ROOT_VERSION_CODE >= ROOT_VERSION( 6, 31, 0 )
-         // first write was already done, need to update the model
-         ATH_MSG_DEBUG("Adding late attribute " << field_name);
-         auto updater = m_ntupleWriter->CreateModelUpdater();
-         updater->BeginUpdate();
-         updater->AddField( std::move(field) );
-         updater->CommitUpdate();
-#endif
-      } else {
-         m_model->AddField( std::move(field) );
-      }
-      m_attrDataMap[ field_name ] = nullptr;
-   }
-
-
-   /// Supply data address for a given field
-   void RNTupleAuxDynWriter::addFieldValue( const std::string& field_name, void* attr_data )
-   {
-      auto field_iter = m_attrDataMap.find(field_name);
-      if( field_iter == m_attrDataMap.end() ) {
-         std::stringstream msg;
-         msg <<"Attempt to write unknown Field with name: '" << field_name << std::ends;
-         throw std::runtime_error( msg.str() );
-      }
-      // already started writing
-      field_iter->second = attr_data;
-      m_needsCommit = true;
-   }   
-
-
-   int RNTupleAuxDynWriter::commit()
-{
-#if ROOT_VERSION_CODE >= ROOT_VERSION( 6, 31, 0 )
-      // write only if there was data added, ignore empty commits
-      if( !needsCommit() ) {
-         ATH_MSG_DEBUG("Empty Commit");
-         return 0;
-      }
-      ATH_MSG_DEBUG("Commit, row=" << m_rowN << " : " << m_ntupleName );
-      if( !m_entry ) makeNewEntry();
-      // update index field before commit 
-
-      int num_bytes = 0;
-      ATH_MSG_DEBUG(m_ntupleName << " has " <<  m_attrDataMap.size() << " attributes");
-      int attrN = 0;
-      for( auto& attr: m_attrDataMap ) {
-         ATH_MSG_VERBOSE("Setting data ptr for field# " << ++attrN << ": " << attr.first << "  data=" << std::hex << attr.second << std::dec );
-         // If an object already exists bind it to the field
-         // Otherwise, create a new value and use its address
-         if( attr.second ) {
-            m_entry->BindRawPtr( attr.first, attr.second );
-         } else {
-            m_entry->EmplaceNewValue( attr.first );
-            attr.second = m_entry->GetPtr<void>( attr.first ).get();
-         }
-      }
-      num_bytes += m_ntupleWriter->Fill( *m_entry );
-      ATH_MSG_DEBUG("Filled RNTuple Row, bytes written: " << num_bytes);
-
-      m_entry.reset();
-      // forget all values to see if any object is missing in a new commit
-      for( auto& attr: m_attrDataMap ) { attr.second = nullptr; } 
-      m_needsCommit = false;
-      m_rowN++;
-
-      return num_bytes;
-#else
-      ATH_MSG_WARNING("Commit not implemented for this ROOT version");
-      return 0;
-#endif
-   }
-
-
-   void RNTupleAuxDynWriter::close() {
-      // Print metrics if enabled - this can become DEBUG/VERBOSE
-      if( m_ntupleWriter->GetMetrics().IsEnabled() ) {
-        auto& log = msg(MSG::INFO);
-        log << "Printing I/O Statistics\n";
-        m_ntupleWriter->GetMetrics().Print(log.stream());
-        log << endmsg;
-      }
-      // delete the generated default fields (RField should delete the default data objest)
-      m_ntupleWriter.reset(); m_entry.reset(); m_model.reset(); m_rowN=0;
-   }
-
-
-   RNTupleAuxDynWriter::~RNTupleAuxDynWriter() {}
-
-}// namespace
-
-
-
+namespace RootAuxDynIO {
+/// Simple Constructor
+RNTupleAuxDynWriter::RNTupleAuxDynWriter()
+    : AthMessaging(std::string("RNTupleAuxDynWriter")) {}
+
+/// Collect Aux data information to be written out
+std::vector<attrDataTuple> RNTupleAuxDynWriter::collectAuxAttributes(
+    const std::string& base_name, SG::IAuxStoreIO* store) {
+  std::vector<attrDataTuple> result;
+  const SG::auxid_set_t selection = store->getSelectedAuxIDs();
+  ATH_MSG_DEBUG("Writing " << base_name << " with " << selection.size()
+                           << " Dynamic attributes");
+  for (SG::auxid_t id : selection) {
+    const std::string attr_type =
+        SG::normalizedTypeinfoName(*store->getIOType(id));
+    const std::string attr_name = SG::AuxTypeRegistry::instance().getName(id);
+    const std::string field_name =
+        RootAuxDynIO::auxFieldName(attr_name, base_name);
+    void* attr_data ATLAS_THREAD_SAFE = const_cast<void*>(store->getIOData(id));
+
+    result.emplace_back(field_name, attr_type, attr_data);
+  }
+  return result;
+}
+
+}  // namespace RootAuxDynIO
diff --git a/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.h b/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.h
index d7fa4f30e5d58bf231fc2577269b9fd1267cbb09..1c4d7e8bef18419caef1b6146d9254ba233a8302 100644
--- a/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.h
+++ b/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.h
@@ -8,97 +8,24 @@
 #include "AthenaBaseComps/AthMessaging.h"
 #include "RootAuxDynIO/RootAuxDynIO.h"
 
-#include "ROOT/RNTuple.hxx"
-#include "ROOT/RField.hxx"
-#include "ROOT/REntry.hxx"
-#if ROOT_VERSION_CODE >= ROOT_VERSION( 6, 31, 0 )
-#include "ROOT/RNTupleWriter.hxx"
-#include "ROOT/RNTupleWriteOptions.hxx"
-#else
-#include "ROOT/RNTupleOptions.hxx"
-#endif
-
-namespace ROOT { namespace Experimental {
-   class RNTupleModel;
-} }
-
-namespace SG { class IAuxStoreIO; }
-
-
-namespace RootAuxDynIO
-{
-#if ROOT_VERSION_CODE >= ROOT_VERSION( 6, 31, 0 )
-   using RFieldBase    = ROOT::Experimental::RFieldBase;
-#else
-   using RFieldBase    = ROOT::Experimental::Detail::RFieldBase;
-#endif
-   using RNTupleWriter = ROOT::Experimental::RNTupleWriter;
-   using RNTupleModel  = ROOT::Experimental::RNTupleModel;
-   using REntry        = ROOT::Experimental::REntry;
-   using RNTupleWriteOptions = ROOT::Experimental::RNTupleWriteOptions;
-
-   
-   class RNTupleAuxDynWriter : public AthMessaging, public IRNTupleWriter
-   {
-   public:
-
-      // store data ptr for the first row, when only creating the model
-      std::map<std::string, void*>        m_attrDataMap;
-
-      std::unique_ptr<RNTupleModel>       m_model;
-      std::unique_ptr<REntry>             m_entry;
-      std::unique_ptr<RNTupleWriter>      m_ntupleWriter;
-
-      std::string          m_ntupleName;
-      TFile*               m_tfile;
-      RNTupleWriteOptions  m_opts;
-      int                  m_rowN = 0;
-
-      /// Count how many APR Containers are writing to this RNTuple (more than one makes a Group)
-      int                  m_clients = 0;
-      bool                 m_needsCommit = false;
+namespace SG {
+class IAuxStoreIO;
+}
 
-      /// Enable/Disable Metric Collection
-      bool                 m_collectMetrics;
+namespace RootAuxDynIO {
 
-      RNTupleAuxDynWriter(TFile* file, const std::string& ntupleName, bool enableBufferedWrite, bool enableMetrics);
+class RNTupleAuxDynWriter : public AthMessaging, public IRNTupleAuxDynWriter {
+ public:
+  /// Default Constructor
+  RNTupleAuxDynWriter();
 
-      /// Create a new empty RNTuple row with the current model (fields)
-      void  makeNewEntry();
+  /// Default Destructor
+  virtual ~RNTupleAuxDynWriter() = default;
 
-      /// handle writing of dynamic xAOD attributes of an object - called from Container::writeObject()
-      //  throws exceptions
-      virtual int writeAuxAttributes( const std::string& base_branch, SG::IAuxStoreIO* store, size_t /*rows_written*/ ) override final;
+  /// Collect Aux data information to be written out
+  virtual std::vector<attrDataTuple> collectAuxAttributes(
+      const std::string& base_branch, SG::IAuxStoreIO* store) override final;
+};
 
-      /// Add a new field to the RNTuple, collect the data pointer for the commit
-      void addAttribute( const std::string& field_name, const std::string& attr_type, void* attr_data );
-
-      /// Add a new field to the RNTuple
-      virtual void addField( const std::string& field_name, const std::string& attr_type ) override;
-
-      /// Supply data address for a given field
-      virtual void addFieldValue( const std::string& field_name, void* attr_data ) override;
-
-      virtual int commit() override final;
-
-      virtual const std::string& getName() const override { return m_ntupleName; }
-
-      virtual size_t size() const override { return m_rowN; }
-
-      /// Check if any data needs to be committed
-      virtual bool needsCommit() const override final { return m_needsCommit; }
-
-      /// Is this RNTuple used by more than one APR container?
-      virtual bool isGrouped() const override final { return m_clients > 1; }
-
-      /// Keep track of how many APR containers are writing to this RNTuple
-      virtual void increaseClientCount() override final { m_clients++; }
-
-      virtual void close() override;
-      
-      virtual ~RNTupleAuxDynWriter();
-   };
-
-}// namespace
+}  // namespace RootAuxDynIO
 #endif
-
diff --git a/Database/AthenaRoot/RootAuxDynIO/src/RootAuxDynIO.cxx b/Database/AthenaRoot/RootAuxDynIO/src/RootAuxDynIO.cxx
index 1c388fa85abc11859efe76dd8c4640e4101d1a7e..36f0d1399f08d9735418be1081875f1e05615f1d 100644
--- a/Database/AthenaRoot/RootAuxDynIO/src/RootAuxDynIO.cxx
+++ b/Database/AthenaRoot/RootAuxDynIO/src/RootAuxDynIO.cxx
@@ -96,9 +96,8 @@ namespace RootAuxDynIO
       return std::make_unique<TBranchAuxDynWriter>(tree, bufferSize, splitLevel, offsettab_len, do_branch_fill);
    }
 
-   std::unique_ptr<RootAuxDynIO::IRNTupleWriter>
-   getNTupleAuxDynWriter(TFile* file, const std::string& ntupleName, bool enableBufferedWrite, bool enableMetrics) {
-      return std::make_unique<RNTupleAuxDynWriter>(file, ntupleName, enableBufferedWrite, enableMetrics);
+   std::unique_ptr<RootAuxDynIO::IRNTupleAuxDynWriter>
+   getNTupleAuxDynWriter() {
+      return std::make_unique<RNTupleAuxDynWriter>();
    }
-
 }