VisualStudio2017 で SalesForce の SOAP API を叩く(Partner版)

# やりたいこと

C# で SOAPAPI を利用して SalesForce 上のオブジェクトを操作
公式ドキュメントは[こちら](https://developer.salesforce.com/docs/atlas.ja-jp.api.meta/api/sforce_api_quickstart_intro.htm)

## Partner 版

- 弱い型付け
- typo に弱い
- カスタムオブジェクト、項目に左右されない

Enterprise 版の記事は[こちら](https://qiita.com/haserror/items/498f54d4cf5bae2e8c55)

# 事前準備

[Enterprise版](https://qiita.com/haserror/items/498f54d4cf5bae2e8c55#%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99)とほぼ同じだけど
WSDL は **Partner** 版を選択


# 実装

## ログイン/ログアウト

[Enterprise版](https://qiita.com/haserror/items/498f54d4cf5bae2e8c55#%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E3%83%AD%E3%82%B0%E3%82%A2%E3%82%A6%E3%83%88)と同じ

## オブジェクト操作

例として Contact の操作

``` C#:Read.cs

/// <summary>
/// Contact取得
/// </summary>
private void getContacts()
{
    // SQL発行
    String soqlQuery = "SELECT Id, FirstName, LastName FROM Contact";
    try
    {
        // デフォルトだと500件までしか取れない
        QueryResult qr = binding.query(soqlQuery);

        string msg = "";
        while (true)
        {
            sObject[] records = qr.records;
            for (int i = 0; i < records.Length; i++)
            {
                sObject con = records[i];
                // Idはどのデータにも存在する
                // あとはSELECT時のカラム番号で参照
                msg += con.Id + ":" + con.Any[2].InnerText + " " + con.Any[1].InnerText + "\r\n";
            }

            if (qr.done)
            {
                // おわり
                break;
            }
            else
            {
                // 500件以上存在する場合、次の500件を取得
                qr = binding.queryMore(qr.queryLocator);
            }
        }

        MessageBox.Show(msg);
    }
    catch (Exception ex)
    {
        // エラー
        MessageBox.Show(ex.Message);
    }
}
```

``` C#:Upsert.cs

/// <summary>
/// Contact更新
/// </summary>
public void upsertContacts()
{
    // 更新対象配列作成(テストなので適当)
    sObject[] upserts = new sObject[1];

    sObject c0 = new sObject();
    c0.type = "Contact";        // オブジェクト指定

    // XML作成
    System.Xml.XmlElement[] contactFields = new System.Xml.XmlElement[4];
    System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
    // 氏
    contactFields[0] = doc.CreateElement("LastName");
    contactFields[0].InnerText = "てすと";
    // 名
    contactFields[1] = doc.CreateElement("FirstName");
    contactFields[1].InnerText = "たろう";
    // Email
    contactFields[2] = doc.CreateElement("Email");
    contactFields[2].InnerText = "test@example.com";
    // 勤務先
    contactFields[3] = doc.CreateElement("AccountId");
    contactFields[3].InnerText = "001000000000000";
    
    c0.Any = contactFields;

    upserts[0] = c0;

    try
    {
        // Email一致の場合UPDATE、それ以外の場合INSERT
        UpsertResult[] upsertResults = binding.upsert("Email", upserts);

        string msg = "";

        foreach (UpsertResult result in upsertResults)
        {
            if (result.success)
            {
                msg += result.id + " : " + (result.created ? "Insert" : "Update") + "\r\n";
            }
            else
            {
                MessageBox.Show("Error!: " + result.errors[0].message);
            }
        }

        MessageBox.Show(msg);
    }
    catch (SoapException e)
    {
        MessageBox.Show(e.Message);
    }
}
```


# おわりに

SalesForce のオブジェクトいじっても WSDL 再生成しなくていいから楽！
でも型がないのちょっとこわい