SalesforceでSOAP APIを使ったレコードの作成

Visual StudioとC#でSalesforceのAPIを使ったデータの作成方法を紹介します。

前回、「SalesforceでSOAP APIを使ったデータ読み込み」では、データを取得してIDと名前を表示するところまで実装しました。今回はこのコードの続きとして、レコードを作成する処理を実装していきます。

レコード作成メソッド

まずはレコードを作成する処理を作っていきます。ここで気をつけなければいけないことは、Salesforceでは1回のAPI呼び出しで操作できるレコードが200件までということです。つまり、200件以上のデータを更新する場合、APIの呼び出しを分けなければなりません。

以上のことを考慮してレコード作成のメソッドを作成してみます。

public void Create<T>(List<T> list) where T : Sforce.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);
		}
	}
}

まずはGenericのCreateメソッドで更新対象が入ったListを受け取ります。そのListに対して、面倒ですが、200件ごとに処理をするように別のListに入れ直してCreateObjectsメソッドに処理を渡しています。

CreateObjectsメソッドでは、bindingのcreate処理を呼び出してデータを渡してあげますが、createの引数はsObject型の配列になっていますので、ToArrayメソッドで配列型に変換して指定しています。sObject型の配列に限定するためにGenericのwhereでsObject型を指定しています。

createメソッドはSaveResult型の結果配列を返してきます。成功しても失敗しても返してきます。この配列でエラーがあった場合はエラーログを出力する処理を追加します。ここでは開発環境の出力ウィンドウに出していますが、実際の本番の業務システムを作る場合は、イベントログに吐くなり、エラー用のデータベースのテーブルを用意してそこに吐くなりして、エラーを通知する仕組みを用意することをオススメします。

前回の記事と同様に例外処理はテキトーにthrowしているだけなので、こちらも実際の本番のシステムで使用するときは、ちゃんとエラーを出力して通知する仕組みを用意しておきましょう。

メインの作成処理

メインの処理ではオブジェクトのレコードを作成してList型に入れ、それを上で作成したCreateメソッドに渡してあげます。ここでは商談オブジェクトのレコードを作成していきます。

using (var salesforce = new SalesforceAccess(userName, password, securityToken))
{
	if (!salesforce.Login()) return;
	var opportunities = new List<Opportunity>();
	var opportunity1 = new Opportunity
	{
		Name = "商談の名前 その1",
		StageName = "Qualification",
		CloseDateSpecified = true,
		CloseDate = DateTime.Now.AddMonths(1)
	};
	opportunities.Add(opportunity1);

	var opportunity2 = new Opportunity
	{
		Name = "商談の名前 その2",
		StageName = "Prospecting",
		CloseDateSpecified = true,
		CloseDate = DateTime.Now.AddMonths(2)
	};
	opportunities.Add(opportunity2);

	salesforce.Create(opportunities);
}

usingの処理の部分は前回解説していますので、今回は中身を解説していきます。まず商談オブジェクトのList型を作成します。ここに作成する商談レコードを格納していきます。

今回は2件の商談を作成します。ここでは簡単にベタ打ちしていますが、本来は外部のデータソースから取得して作成することが多いと思いますので、もう少し複雑な処理になります。

しかし、基本は変わりません。商談のクラスを作成して、商談の情報をそれぞれの項目に代入し、List型に渡して先ほど作成したCreateメソッドを実行します。その中で、値型の項目にデータを渡すとき<項目名>Specifiedプロパティをtrueにしてあげないとデータが更新されませんので注意してください。

完成形コード

それでは完成形のコードを紹介します。まずはメインの処理です。

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 = new List<Opportunity>();
				var opportunity1 = new Opportunity
				{
					Name = "商談の名前 その1",
					StageName = "Qualification",
					CloseDateSpecified = true,
					CloseDate = DateTime.Now.AddMonths(1)
				};
				opportunities.Add(opportunity1);

				var opportunity2 = new Opportunity
				{
					Name = "商談の名前 その2",
					StageName = "Prospecting",
					CloseDateSpecified = true,
					CloseDate = DateTime.Now.AddMonths(2)
				};
				opportunities.Add(opportunity2);

				salesforce.Create(opportunities);
			}
		}
	}
}

メインの処理はSalesforceにログインし、商談レコードを作成して、Createメソッドを呼び出しているだけです。特に注意するところはないでしょう。

次にSalesforceへのAPIのクラスです。

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 : Sforce.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);
				}
			}
		}
	}
}

こちらは前回の継ぎ足しで書いていますので、Login、Logout、Getメソッドたちが登場しています。今回の記事で呼び出しているのはLogin、Logout、Create、CreateObjectsたちです。

まとめ

今回はSalesforceに商談レコードを作成する処理を実装しましたが、これは取引先でもリードでも、カスタムオブジェクトでも使うことができます。カスタムオブジェクトの場合、カスタムオブジェクトを作成したあとでEnterprise WSDLをVisual Studioに取り込んであげれば、カスタムオブジェクト型のクラスが登場します。

一番のポイントは1回のAPI呼び出して200件までということ。次に値型の項目は<項目名>Specifiedプロパティをtrueにすること。これらを注意しておけば大丈夫です。

実際、インテグレーションではデータをSalesforce上に作成するだけことが多いと思いますので1、今回までの記事で実際に業務に耐えうるシステムを作り上げることができると思います。

  1. 一度作ったデータは触らないというルールにしておけばね。

Follow me!

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

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