ループ内でnewView()を使用しない
ループ内でnewView()を使用すると、単一のトリガーまたはオブジェクト関数で許可されているより多くのビュー・オブジェクトを誤って作成した場合に、予期しないExprResourceExceptionエラーが発生する可能性があります。
たとえば、不明な数の未清算トランザクション・レコードを反復する次のコードを考えてみます。 処理された未決済トランザクションごとに、トランザクション通貨がGBP
でない場合は、トランザクション日に基づいて過去の為替レートを問い合せ、問題のGBP
以外の通貨金額をGBP
に換算します。 ループ内のnewView()
関数を使用してExchangeRate_c
ビジネス・オブジェクトを問い合せ、バインド変数を使用せずに実行します。 このアプローチでは、ループの反復ごとに新しいビュー・オブジェクトが1つ作成されます。 反復されるトランザクション行の数が予測できないほど大きい場合、この方法では、単一のトリガーまたは関数で作成できるビュー・オブジェクト数の上限に達したときにExprResourceException
エラーが発生することがあります。
// NON-BEST-PRACTICE EXAMPLE: USES newView() INSIDE A LOOP !!
// ~~~~~~~~~~~~~~~~~~~~~~~~~ May lead to unpredictable ExprResourceException error
// Create view object for processing uncleared transactions
def txns = newView('Transaction_c')
txns.appendViewCriteria("Cleared_c = 'N'")
selectAttributesBeforeQuery(txns,['Id','Cleared_c','Currency_c','Amount_c','Date_c'])
txns.executeQuery()
// Process each uncleared transaction
while (txns.hasNext()) {
def rate = 1
def txn = txns.next()
def curr = txn.Currency_c
if (curr != 'GBP') {
def date = txn.Date_c
// NON-BEST PRACTICE: USE OF newView() INSIDE A LOOP !!
def rates = newView('ExchangeRate_c')
rates.appendViewCriteria("From_c = '${curr}' and To_c = 'GBP' and Date_c = '${date}'")
rates.executeQuery()
rate = rates.first()?.Rate_c
}
if (rate) {
txn.Cleared_c = 'Y'
// Multiply original txn amount by rate and round to 2 decimal places
txn.AmountInGBP_c = (txn.Amount_c * rate as Double).round(2)
}
}
- フィルタ基準でバインド変数を参照する、ループの外側に単一ビュー・オブジェクトを作成
- ループ内で、現在のループ反復のバインド変数の値を設定
- ループ反復ごとに1回、単一ビュー・オブジェクトに対して問合せを実行
この手法を採用することで、予測できない多数のビュー・オブジェクトではなく単一のビュー・オブジェクトを使用し、多数の行を反復する際にExprResourceException
が発生しないようにします。 次のコードは、前述と同じ機能を実装していますが、これらのベスト・プラクティス・ガイドラインに従います。
// BEST PRACTICE: Single VO with bind variables outside the loop
// ~~~~~~~~~~~~~
// Create view object to be reused inside the loop for exchange rates
def rates = newView('ExchangeRate_c')
addBindVariable(rates,'Base','Text')
addBindVariable(rates,'ForDate','Date')
rates.appendViewCriteria("From_c = :Base and To_c = 'GBP' and Date_c = :FromDate")
// Create view object for processing uncleared transactions
def txns = newView('Transaction_c')
txns.appendViewCriteria("Cleared_c = 'N'")
selectAttributesBeforeQuery(txns,['Id','Cleared_c','Currency_c','Amount_c','Date_c'])
txns.executeQuery()
// Process each uncleared transaction
while (txns.hasNext()) {
def rate = 1
def txn = txns.next()
def curr = txn.Currency_c
if (curr != 'GBP') {
def date = txn.Date_c
// BEST PRACTICE: Set bind variables & execute view object created outside loop
setBindVariable(rates,'Base',curr)
setBindVariable(rates,'ForDate',date)
rates.executeQuery()
rate = rates.first()?.Rate_c
}
if (rate) {
txn.Cleared_c = 'Y'
// Multiply original txn amount by rate and round to 2 decimal places
txn.AmountInGBP_c = (txn.Amount_c * rate as Double).round(2)
}
}
ループ内の機能がより複雑になる場合は、それをオブジェクト関数にリファクタすることでメリットが得られます。 次のオブジェクト関数は、ループの外部で作成された単一ビュー・オブジェクトをタイプObject
のパラメータとして受け入れるexchangeRateForCurrencyOnDate()
ヘルパー関数を示しています。 関数内では、バインド変数を設定し、ビュー・オブジェクトの問合せを実行し、結果の換算レートを返します。
オブジェクト関数: Float exchangeRateForCurrencyOnDate( Object rates, String curr, Date date)
// Set bind variables and execute view object passed in
setBindVariable(rates,'Base',curr)
setBindVariable(rates,'ForDate',date)
rates.executeQuery()
return rates.first()?.Rate_c
このオブジェクト関数にコードをリファクタリングした後、前述の元のベスト・プラクティス・コードのifブロックを次のように変更できます:
// etc.
if (curr != 'GBP') {
def date = txn.Date_c
// Pass single 'rates' view object into the helper function
rate = exchangeRateForCurrencyOnDate(rates,curr,date)
}
// etc.
「ビジネス・ロジック問合せの簡略化」で説明されているqueryMaps()
またはqueryRows()
ヘルパー関数を使用すると、このセクションで説明する最適化されたベスト・プラクティスの手法を、次のようなコード行を減らして実行できます:
// BEST PRACTICE: Create a reusable query for looking up the exchange rate using
// ~~~~~~~~~~~~~ bind variable values of the correct datatype. Inside the loop
// set the correct bind var values and execute the same view object over & over
def rateVO = adf.util.query(
select: 'Rate_c',
from: 'ExchangeRate_c',
where: "From_c = :Base and To_c = 'GBP' and Date_c = :ForDate",
binds: [Base: 'XXX', ForDate: today() ])
// Will be updating the queried rows, so use queryRows()
for (txn in adf.util.queryRows(select:'Id,Cleared_c,Currency_c,Amount_c,Date_c',
from: 'Transaction_c',
where: "Cleared_c = 'N'")) {
def rate = 1
// If transaction currency is different than GBP, lookup historical
// exchange rate for the date of the transaction to convert the
// transaction currency into GBP
if (txn.Currency_c != 'GBP') {
// Set any bind variables to new values for current loop iteration
setBindVariable(rateVO,'Base',txn.Currency_c)
setBindVariable(rateVO,'ForDate',txn.Date_c)
// Execute the same view object with the new bind variable values
rateVO.executeQuery()
rate = rateVO.first()?.Rate_c
}
if (rate) {
txn.Cleared_c = 'Y'
// Multiply original txn amount by rate and round to 2 decimal places
txn.AmountInGBP_c = (txn.Amount_c * rate as Double).round(2)
}
}