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単位で追加できますが、結構な料金が発生しますのでよく考えたほうがいいでしょう。

Follow me!

Feedlyで新着記事をチェックしよう!

Feedlyでフォローしておけば、新着記事をチェックすることができます。ぜひ、この機会にFeedlyに追加しておきましょう。