2

Recently I have been trying my hand at using the SOAP API (C#) to automate a data-migration between two SFDC instances.

Because the destination instance is still being built, I am trying to use the Partner instead of the Enterprise. The benefit is that I can use the same C# class to handle both sides, and don't need to generate a new Class every time an administrator adds a field (avoiding errors like THIS with each change).

That being said, I was very disapointed with the experience of the Partner API when it came to Accessing sObjects (See Using the Partner WSDL). Specifically not having access to sObject.get(fieldName) or sObject.put(fieldName, value).

It seems that when using Java there is a sObject.getField(fieldName) (see the java example on Page 100 of the SOAP API Documentation) but this doesn't exist in c#.

That being said, I am trying to extend the sObject functionality to add these features. The following seems to work.

namespace sforce{
  public partial class sObject{
    public dynamic get(string s){
      // we use the standard sObject.Any property to iterate over sobject Attributes
      for (int i = 0; i < Any.Length; i++){
        System.Xml.XmlElement e = Any[i];
        if (e.LocalName == s)
          if (e.Attributes.Count == 0)
            return Any[i].InnerText;
          else
            return convert(e.ChildNodes);
      }
      return null;
    }
    // Create a private method to convert parent Related Objects
    private sObject convert(System.Xml.XmlNodeList e){
      sObject o = new sObject();
      o.type = e[0].InnerText;
      System.Xml.XmlElement[] fields = new System.Xml.XmlElement[e.Count - 1];
      System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
      for (int i = 1; i < e.Count; i++){
        fields[i - 1] = doc.CreateElement(e[i].LocalName);
        fields[i - 1].InnerText = e[i].InnerText;
        fields[i - 1].Prefix = e[i].Prefix;
      }
      o.Any = fields;
      return o;
    }
  }
}

Note: "sforce" above is the namespace I am using for the C# Proxy class generated from the Partner WSDL.

this can then be called as follows:

String soqlQuery = "SELECT Id, FirstName, LastName, AccountId, Account.id, Account.Name FROM Contact";
QueryResult qr = binding.query(soqlQuery);
if (qr.size > 0){
  sObject[] records = qr.records;
  for (int i = 0; i < records.Length; i++){
    sObject con = records[i];
    string fName = con.get("FirstName");
    sObject account = con.get("Account");
    Console.WriteLine("Contact: " + fName);
    Console.WriteLine("Account Name: " + account.get("Name") + " : " + account.get("Id");
  }
}

My questions here are:

  1. Is there a native way to have this functionality from C# without having to write it?
  2. Assuming there isn't, is there a general case where this approach would not work?
1

I've been using the Partner API for some time now from C#. I'm not aware of any native way to quickly ready the field values from the sObject result. You will need to either write something yourself or use someones library.

A few comments on your get implementation.

  1. When checking if the LocalName equals the field name you are looking for. I'd use .Equals with StringComparison.InvariantCultureIgnoreCase. It should be case insensitive, and you don't want to be dealing with specific cultures ideas of equality.
  2. You can check the XmlElement IsEmpty property or if it has the "nil" attribute as true to determine if the result should be null.

In your convert method. Maybe copy over any attributes if they exist.

  • Thanks for your confirmation and feedback. I am not sure I understand your comment about copying over attributes if they exist. the for loop in the convert() was intended to replicate the attributes. From my initial testing, it seems to work. Is it not going to do what I think it is? – Tezyn Nov 22 '15 at 22:24
  • I might be misreading how convert works. To me it looks like it is iterating over the child node XmlElements and duplicating out new XmlElements with the InnerText and Prefix set. However, if any of those child XmlElements had Attributes themselves those values wouldn't be copied over. – Daniel Ballinger Nov 22 '15 at 23:25
  • From what I could tell, e.ChildNodes returns XMLNodeList where the first node[0] is the Name of the Related Object, and node[x] > 0 are the Attributes themselves. This was the only property that I could find that gave everything that I needed to extrapolate the related sObject and the Attributes. I don't really like it, so if there is a better one, I would gladly change it... Just all I could find. – Tezyn Nov 22 '15 at 23:44
1

I've taken what you've found and added a little more to suit my needs.

This extension makes it a little more robust for more complex SOQL Queries.

 public static class SObjectExtension
{

    public static dynamic Get(this sObject sObject,string s)
    {
        foreach (var xmlElement in sObject.Any)
        {
            var e = xmlElement;

            if (e.LocalName != s) continue;

            if (e.IsEmpty)
            {
                return null;
            }

            if (e.Attributes.Count == 0)
                return xmlElement.InnerText;

            if (e.GetAttribute("xsi:type") == "QueryResult")
            {
                return ConvertToQueryResult(e.ChildNodes);
            }

            return ConvertToSObject(e.ChildNodes);
        }
        return null;
    }

    private static QueryResult ConvertToQueryResult(System.Xml.XmlNodeList e)
    {
        var recordsList = new List<sObject>();

        for (var i=0; i < e.Count; i++)
        {
            if (e[i].LocalName == "records")
            {
                recordsList.Add(ConvertToSObject(e[i].ChildNodes));
            }
        }

        return new QueryResult()
        {
            done = true,
            queryLocator = null,
            records = recordsList.ToArray(),
            size = recordsList.Count
        };

    }

    private static sObject ConvertToSObject(System.Xml.XmlNodeList e)
    {
        var o = new sObject {type = e[0].InnerText};
        var fields = new System.Xml.XmlElement[e.Count - 1];
        var doc = new System.Xml.XmlDocument();

        for (var i = 1; i < e.Count; i++)
        {

            fields[i - 1] = doc.CreateElement(e[i].LocalName);
            fields[i - 1].InnerText = e[i].InnerText;
            fields[i - 1].Prefix = e[i].Prefix;

            if (e[i].HasChildNodes)
            {
                    fields[i - 1] = (XmlElement) e[i].CloneNode(true);                 
            }

        }
        o.Any = fields;
        return o;
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.