SalesforceでSOAP APIを使ったレコードの更新
Salesforce APIを使ったレコードの更新方法を紹介します。
前回は「SalesforceでSOAP APIを使ったレコードの作成」という記事でレコードの作成方法を紹介しました。今回は作成したレコードを取得して、内容を変更して更新するという処理を作っていきます。
レコード更新メソッド
作成のときにも書きましたけど、更新の場合も同じで1回のAPI呼び出しで200件までのレコードしか扱うことができません。更新の場合も200件ごとに更新処理をしていく必要があります。
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);
}
}
}
更新の場合も作成とほぼ同じ内容です。binding.createがbinding.updateに変わるだけの簡単なお仕事です。エラーのときの処理もまったく同じように書いています。この辺の詳しい説明は前回していますので、気になる場合はそちらを参照してください。
メインの更新処理
メインの処理では、商談レコードを取得して、内容を変更してUpdateメソッドに渡していきます。
using (var salesforce = new SalesforceAccess(userName, password, securityToken))
{
if (!salesforce.Login()) return;
var opportunities = salesforce.Get<Opportunity>("SELECT Id, Name, StageName, CloseDate FROM Opportunity WHERE Name LIKE '商談の名前%'");
foreach (var opportunity in opportunities)
{
opportunity.StageName = "Needs Analysis";
}
salesforce.Update(opportunities);
}
今回は、前回作成した商談レコードを取得し、フェーズを「Needs Analysis」に変更しています。取得する際に必ずIdを入れるようにしてください。このIdがSalesforce上でレコードを特定するための、全世界で一意にレコードを識別できるSalesforce IDです。
このIdはSalesforce上でレコードを表示したときにURLに含まれているので、レコードを表示したときのURLをお気に入りに入れておくと、いつでもそのレコードにアクセスすることができます。レポートでIdを出力する場合、商談オブジェクトなら「商談 ID」、リードオブジェクトなら「リード ID」という名前の項目を出力項目に含めてあげれば、一覧で出すことができます。
完成形コード
完成形のコードを書いていきます。まずはメインの処理。
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, Name, StageName, CloseDate FROM Opportunity WHERE Name LIKE '商談の名前%'");
foreach (var opportunity in opportunities)
{
opportunity.StageName = "Needs Analysis";
}
salesforce.Update(opportunities);
}
}
}
}
メインの処理では、初回に紹介したGetメソッドでデータを取得しています。取得する対象は商談オブジェクトで、取得項目はId、Name、StageName、CloseDateです。日本語に置き換えると「商談 ID」「商談名」「フェーズ」「完了予定日」です。条件としてLIKEを使って、商談名に対して「商談の名前」という文字を前方一致で指定しています。取得項目と条件設定はSOQLを使用して書いています。
取得したデータのフェーズを「Needs Analysis」に変更してUpdateメソッドに渡しています。この辺はそれほど難しい処理ではないかと思います。
次に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);
}
}
}
}
}
Getメソッド、Createメソッドに、今回のUpdateメソッドが加わって少し長くなってきています。Updateメソッドも特に説明は必要ないと思います。Createメソッドとほぼ同じでbinding.updateのところだけが違うだけです。
まとめ
実際に外部のシステムと連携する場合、対象のオブジェクトにカスタム項目として外部IDを保存するための項目を作っておき、レコード作成時に外部システムのIDも一緒に保存しておきます。こうすると、外部システム側でデータの変更があった場合、外部IDをキーにして更新対象のレコードを特定することができます。
また、Salesforceのデータストレージは基本的に少なめなので、あらゆるデータを保存しようとするのではなく、よく吟味してデータ連携するといいでしょう。1GBとか10GB単位で追加できますが、結構な料金が発生しますのでよく考えたほうがいいでしょう。