SalesforceでSOAP APIを使ったレコードの削除
Salesforce APIを使ったレコードの削除方法を紹介します。
Salesforce SOAP APIシリーズも第4回目を迎えます。第1回目の「SalesforceでSOAP APIを使ったデータ読み込み」、第2回目の「SalesforceでSOAP APIを使ったレコードの作成」、第3回目の「SalesforceでSOAP APIを使ったレコードの更新」に引き続き、今回はレコードの削除をお届けします。
第2回目で作成し、第3回目で更新した商談レコードを今回は削除していまします。
レコード削除メソッド
それでは、まずは削除メソッドを作っていきます。
public void Delete(List<string> list)
{
try
{
var deleteList = new List<string>();
list.ForEach(item =>
{
deleteList.Add(item);
if (deleteList.Count == 200)
{
DeleteObjects(deleteList);
deleteList = new List<string>();
}
});
if (deleteList.Count > 0)
{
DeleteObjects(deleteList);
}
}
catch (SoapException)
{
throw;
}
catch (Exception)
{
throw;
}
}
private void DeleteObjects(List<string> deleteList)
{
var results = binding.delete(deleteList.ToArray());
var errors = results.ToList().FindAll(result => !result.success);
if (errors.Count > 0)
{
foreach (var error in errors)
{
string message = string.Empty;
error.errors.ToList().ForEach(e =>
{
message += e.message + System.Environment.NewLine;
});
System.Diagnostics.Debug.WriteLine("ID: {0}, Message: {1}", error.id, message);
}
}
}
今回、注目は取り扱うデータの型です。取得、作成、更新のときはsObject型を扱っていましたが、今回はstring型です。というのも、deleteメソッドはstring配列を渡せばよく、string配列にはSalesforce IDの配列を入れてあげればいいだけだからです。
逆にいうと、あらかじめIdがわかっているか、削除対象のIdを取得してこなければいけないことになります。取得してくる場合は第1回目のときのGetメソッドが使えます。
メインの削除処理
メインの処理では商談レコードから削除したいデータを取得して、Idだけを取り出し、string配列にしてDeleteメソッドに渡してあげます。
using (var salesforce = new SalesforceAccess(userName, password, securityToken))
{
if (!salesforce.Login()) return;
var opportunities = salesforce.Get<Opportunity>("SELECT Id FROM Opportunity WHERE Name LIKE '商談の名前%'");
var list = new List<string>();
opportunities.ForEach(opportunity => list.Add(opportunity.Id));
salesforce.Delete(list);
}
GetメソッドでSOQLを書いていますが、抽出する項目はIdのみを指定しています。Idしか必要がないからです。
なお、削除したレコードはゴミ箱に入ります。ちなみに、Lightning Experienceではゴミ箱は使えません。ゴミ箱にアクセスするにはSalesforce Classicに切り替える必要があります1。ただ、切り替えなくてもドメイン名のうしろに「/search/UndeletePage」とURL直打ちで一時的にゴミ箱だけクラシック画面でアクセスできます。 なんなら、ゴミ箱のURLをお気に入りに入れておけばいいですね。
完成形コード
完成形のコードを紹介します。まずはMainメソッドの処理からです。
using System;
using System.Collections.Generic;
using System.Configuration;
using SalesforceAccessConsoleApp.Sforce;
namespace SalesforceAccessConsoleApp
{
class MainClass
{
public static void Main(string[] args)
{
string userName = ConfigurationManager.AppSettings["SalesforceUserName"];
string password = ConfigurationManager.AppSettings["SalesforcePassword"];
string securityToken = ConfigurationManager.AppSettings["SalesforceSecurityToken"];
using (var salesforce = new SalesforceAccess(userName, password, securityToken))
{
if (!salesforce.Login()) return;
var opportunities = salesforce.Get<Opportunity>("SELECT Id FROM Opportunity WHERE Name LIKE '商談の名前%'");
var list = new List<string>();
opportunities.ForEach(opportunity => list.Add(opportunity.Id));
salesforce.Delete(list);
}
}
}
}
Mainメソッドの中でGetメソッドを使って削除対象のデータを取得して、stringの配列にしてからDeleteメソッドを呼び出しています。これは、上で説明した通りです。
次にDeleteメソッドが加わったSalesforceAccessクラスです。
using System;
using System.Linq;
using System.Collections.Generic;
using System.Web.Services.Protocols;
using SalesforceAccessConsoleApp.Sforce;
namespace SalesforceAccessConsoleApp
{
public class SalesforceAccess : IDisposable
{
SforceService binding;
string userName;
string password;
string securityToken;
bool isLogin = false;
public SalesforceAccess(string loginUserName, string loginPassword, string loginSecurityToken)
{
userName = loginUserName;
password = loginPassword;
securityToken = loginSecurityToken;
}
public void Dispose()
{
Logout();
}
public bool Login()
{
binding = new SforceService();
binding.Timeout = 60000;
LoginResult loginResult;
try
{
loginResult = binding.login(userName, password + securityToken);
}
catch (SoapException)
{
throw;
}
catch (Exception)
{
throw;
}
if (loginResult.passwordExpired)
{
return false;
}
binding.Url = loginResult.serverUrl;
binding.SessionHeaderValue = new SessionHeader();
binding.SessionHeaderValue.sessionId = loginResult.sessionId;
isLogin = true;
return true;
}
public void Logout()
{
if (!isLogin) return;
try
{
binding.logout();
isLogin = false;
}
catch (SoapException)
{
throw;
}
catch (Exception)
{
throw;
}
}
public List<T> Get<T>(string query) where T: Sforce.sObject
{
var list = new List<T>();
QueryResult queryResult;
try
{
queryResult = binding.query(query);
bool done = false;
if (queryResult.size > 0)
{
while (!done)
{
var records = queryResult.records;
for (int i = 0; i < records.Length; i++)
{
var recordType = records[i] as T;
list.Add(recordType);
}
if (queryResult.done) done = true;
else queryResult = binding.queryMore(queryResult.queryLocator);
}
}
}
catch (SoapException)
{
throw;
}
catch (Exception)
{
throw;
}
return list;
}
public void Create<T>(List<T> list) where T : sObject
{
try
{
var createList = new List<T>();
list.ForEach(item =>
{
createList.Add(item);
if (createList.Count == 200)
{
CreateObjects(createList);
createList = new List<T>();
}
});
if (createList.Count > 0)
{
CreateObjects(createList);
}
}
catch (SoapException)
{
throw;
}
catch (Exception)
{
throw;
}
}
private void CreateObjects<T>(List<T> createList) where T : sObject
{
var results = binding.create(createList.ToArray());
var errors = results.ToList().FindAll(result => !result.success);
if (errors.Count > 0)
{
foreach (var error in errors)
{
string message = string.Empty;
error.errors.ToList().ForEach(e =>
{
message += e.message + System.Environment.NewLine;
});
System.Diagnostics.Debug.WriteLine("ID: {0}, Message: {1}", error.id, message);
}
}
}
public void Update<T>(List<T> list) where T: sObject
{
try
{
var updateList = new List<T>();
list.ForEach(item =>
{
updateList.Add(item);
if (updateList.Count == 200)
{
UpdateObjects(updateList);
updateList = new List<T>();
}
});
if (updateList.Count > 0)
{
UpdateObjects(updateList);
}
}
catch (SoapException)
{
throw;
}
catch (Exception)
{
throw;
}
}
private void UpdateObjects<T>(List<T> updateList) where T : sObject
{
var results = binding.update(updateList.ToArray());
var errors = results.ToList().FindAll(result => !result.success);
if (errors.Count > 0)
{
foreach (var error in errors)
{
string message = string.Empty;
error.errors.ToList().ForEach(e =>
{
message += e.message + System.Environment.NewLine;
});
System.Diagnostics.Debug.WriteLine("ID: {0}, Message: {1}", error.id, message);
}
}
}
public void Delete(List<string> list)
{
try
{
var deleteList = new List<string>();
list.ForEach(item =>
{
deleteList.Add(item);
if (deleteList.Count == 200)
{
DeleteObjects(deleteList);
deleteList = new List<string>();
}
});
if (deleteList.Count > 0)
{
DeleteObjects(deleteList);
}
}
catch (SoapException)
{
throw;
}
catch (Exception)
{
throw;
}
}
private void DeleteObjects(List<string> deleteList)
{
var results = binding.delete(deleteList.ToArray());
var errors = results.ToList().FindAll(result => !result.success);
if (errors.Count > 0)
{
foreach (var error in errors)
{
string message = string.Empty;
error.errors.ToList().ForEach(e =>
{
message += e.message + System.Environment.NewLine;
});
System.Diagnostics.Debug.WriteLine("ID: {0}, Message: {1}", error.id, message);
}
}
}
}
}
SalesforceAccessクラスの完成形と言えるものです。ログイン、ログアウト処理、データの取得、作成、更新、削除の処理が網羅された必要十分なクラスです。このクラスがあれば、ほとんどのシステムで用が足りると思います。
まとめ
削除処理ができれば、不要なデータを削除するよう定期的に実行するバッチなんかも組めますので、データストレージの容量対策に古いデータから削除するということも可能になります。
Salesforce SOAP APIシリーズ第4回目でデータの削除を紹介しました。そして、今回の記事の掲載しているSalesforceAccessクラスは、Salesforceとのデータ連携に必要な処理が網羅されていますので、これがあればだいたいのことができるようになります。
あとは、業務に合わせてデータを取得したり、加工して更新したり、削除したりすればいいわけです。例えば、社内のシステムで見積もり処理をしたり、受注の処理を管理しているのであれば、金額や原価などのデータをSalesforceと同期して上げることで、営業担当の入力負荷を軽減することができます。
また、既存のシステムからリプレースする場合、SOAP APIを使ってマイグレーションツールを作成すれば、データ移行をする際に大活躍することになるでしょう。APIを整備して、広く解放しているSalesforce、というかforce.comというプラットフォームだからできることです。
APIを活用して、より効率的にシステムの運用を実現することができます。
- 記事作成時点での情報です。データアクセスおよびビュー: Lightning Experience の制限事項より。 ↩