親子のINSERTをApexでどのように書くか

By | September 12, 2022

概要

商談・商談商品をベースに契約・契約商品を自動作成する場合や、契約・契約商品をベースに請求・請求明細を自動作成する場面など、親レコードと子レコードをセットでInsertしたいという場合に、Apexでどのようなコードを書くかという話

何が問題か

親-子のセットのInsertが一回だけの場合は、以下のようなベタベタの書き方でも特に問題はない。

  1. 親をInsert
  2. 子のインスタンスで親への参照を付ける
  3. 子をInsert
Billing__c bill = 〜;
insert bill;
List<BillingDetails__c> bdList = 〜;
for(BillingDetails__c bd : bdList){
 bd.BillId__c = bill.Id;
}
insert bdList;

しかし、親-子のセットを複数まとめてInsertしたい場合、ガバナ制限を回避しつつ(=DML inside For Loopを回避しつつ)親子の紐付けを担保するにはどのようにすればよいのかという問題が生じる。

List<Billing__c> billList = 〜;
insert billList;
List<BillingDetails__c> bdList = 〜;
for(BillingDetails__c bd : bdList){
 bd.BillId__c = ???;
}
insert bdList;

あり得る実装方法

①forループを二重化する(※アンチパターン)
②Mapを利用する
③ラッパークラスを利用する

forループを二重化する

以下の二つの問題が発生するため、NG

  • DML inside For LoopによるDMLステートメント発行数の増大
  • 計算量の非線形化による、処理時間の増大
List<Billing__c> billList = 〜;
List<BillingDetails__c> bdList = 〜;
for(Billing__c bill : billList){
 insert bill;
 for(BillingDetails__c bd : bdList){
  bd.BillId__c = bill.Id;
 }
}
insert billList;
insert bdList;

Mapを利用する

List<Billing__c> billList = 〜;
insert billList;
Map<String,Id> billIdMap = new Map<String,Id>();
for(Billing__c bill : billList){
 String billKey = bill.Account__r.Name;  
 billIdMap.put(billKey,bill.Id);
}
List<BillingDetails__c> bdList = 〜;
for(BillingDetails__c bd : bdList){
 bd.BillId__c = billIdMap.get(bd.Account__r.Name);
}
insert bdList;

ラッパークラスを利用する

//Wrapperクラスを定義
Class BillRelatedEntity{
 private Billing__c bill;
 private List<BillingDetails__c> bdList;

 public billRelatedEntity(Contract__c con, List<ContractDetails__c>  cdList){
  bill = new Billing__c (〜);
  bdList = new List<BillingDetails__c>();
  for(ContractDetails__c cd : cdList){
   BillingDetails__c bd = new BillingDetails__c();
   bd.A__c = cd.A__c;
   bd.B__c = cd.B__c;
   bd.C__c = cd.C__c;
   bdList.add(bd);
  }
 }

 public Id getBillingId(){
  return this.bill.Id;
 }

 public Billing__c getBilling(){
  return this.bill;
 }

 public List<BillingDetails__c> getBillingDetails(){
  return this.bdList;
 }
}

//契約・契約明細をベースにWrapperクラスのインスタンスを生成し、WrapperのListに格納
List<Contract__c> targetContractList= 〜;
List<BillRelatedEntity> breList = new List<BillRelatedEntity>(); 
for(Contract__c con : targetContractList){
 List<ContractDetails__c> cdList = new List<ContractDetails__c>();
 for(ContractDetails__c cd : con.contracts__r){
  cdList.add(cd);
 }
 BillRelatedEntity bre = new BillRelatedEntity(con,cdList);
 breList.add(bre);
}

//請求の作成
List<Billing__c> billListToInsert = new List<Billing__c>();
for(BillRelatedEntity tmpBre : List<BillRelatedEntity>){
 Billing__c bill = tmpBre.getBilling();
 billListToInsert.add(bill);
}
insert billListToInsert;

//請求明細の作成
List<BillingDetails__c> bdListToInsert = new List<BillingDetails__c>();
for(BillRelatedEntity tmpBre : List<BillRelatedEntity>){
 Id billingId = tmpBre.getBillingId();
 List<BillingDetails__c> bdList = tmpBre.getBillingDetails();
 for(BillingDetails__c tmpBd : bdList){
  tmpBd.Billing__c = billingId;
  bdListToInsert.add(tmpBd);
 }
}
insert bdListToInsert;