diff --git a/SalesforceMagic/Abstract/ISalesforceClient.cs b/SalesforceMagic/Abstract/ISalesforceClient.cs
index f6216c1..123e335 100644
--- a/SalesforceMagic/Abstract/ISalesforceClient.cs
+++ b/SalesforceMagic/Abstract/ISalesforceClient.cs
@@ -21,8 +21,85 @@ public interface ISalesforceClient : IDisposable
#region Query Methods
+ ///
+ /// Simple Query
+ /// - Query items based on generic object
+ /// - Limited by 200 records
+ ///
+ ///
+ ///
+ ///
+ IEnumerable Query(int limit = 0) where T : SObject;
+
+ ///
+ /// Simple Query
+ /// - Query items based on generic object
+ /// - Generate query using predicate
+ /// - Limited by 200 records
+ ///
+ ///
+ ///
+ ///
+ ///
IEnumerable Query(Expression> predicate, int limit = 0) where T : SObject;
+
+ ///
+ /// Simple Query
+ /// - Query items based on generic object
+ /// - Utilize included raw query
+ /// - Limited by 200 records
+ ///
+ ///
+ ///
+ ///
IEnumerable Query(string query);
+
+ ///
+ /// Advanced Query
+ /// - Query items based on generic object
+ /// - Returns query locator, and done status which
+ /// can be used to bypass the 200 record limit.
+ ///
+ ///
+ ///
+ ///
+ QueryResult AdvancedQuery(int limit = 0) where T : SObject;
+
+ ///
+ /// Advanced Query
+ /// - Query items based on generic object
+ /// - Generate query using predicate
+ /// - Returns query locator, and done status which
+ /// can be used to bypass the 200 record limit.
+ ///
+ ///
+ ///
+ ///
+ ///
+ QueryResult AdvancedQuery(Expression> predicate, int limit = 0) where T : SObject;
+
+ ///
+ /// Advanced Query
+ /// - Query items based on generic object
+ /// - Utilize included raw query
+ /// - Returns query locator, and done status which
+ /// can be used to bypass the 200 record limit.
+ ///
+ ///
+ ///
+ ///
+ QueryResult AdvancedQuery(string query);
+
+ ///
+ /// Query More
+ /// - Used to retrieve the next set of records
+ /// available in a query using the queryLocator.
+ ///
+ ///
+ ///
+ ///
+ QueryResult QueryMore(string queryLocator);
+
T QuerySingle(Expression> predicate) where T : SObject;
T QuerySingle(string query);
diff --git a/SalesforceMagic/Entities/QueryResult.cs b/SalesforceMagic/Entities/QueryResult.cs
new file mode 100644
index 0000000..fe2a571
--- /dev/null
+++ b/SalesforceMagic/Entities/QueryResult.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace SalesforceMagic.Entities
+{
+ public class QueryResult
+ {
+ public string QueryLocator { get; set; }
+ public bool Done { get; set; }
+ public IEnumerable Records { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/SalesforceMagic/Entities/sObject.cs b/SalesforceMagic/Entities/sObject.cs
index 9b31663..bad1d4e 100644
--- a/SalesforceMagic/Entities/sObject.cs
+++ b/SalesforceMagic/Entities/sObject.cs
@@ -16,18 +16,14 @@ public abstract class SObject : ISalesforceObject, IXmlSerializable
{
public string Id { get; set; }
- public XmlSchema GetSchema() { return null; }
-
- internal string ToCsv()
+ public XmlSchema GetSchema()
{
- Type type = GetType();
- TypeAccessor accessor = ObjectHydrator.GetAccessor(type);
- string[] values = type.GetProperties().Select(x => GetCsvValue(x, accessor)).ToArray();
-
- return String.Join(",", values);
+ return null;
}
- public void ReadXml(XmlReader reader) { }
+ public void ReadXml(XmlReader reader)
+ {
+ }
public void WriteXml(XmlWriter writer)
{
@@ -38,16 +34,37 @@ public void WriteXml(XmlWriter writer)
foreach (PropertyInfo info in type.GetProperties())
{
- var value = accessor[this, info.Name];
+ object value = accessor[this, info.Name];
if (value == null) continue;
- string xmlValue = value is DateTime
+ string xmlValue = value is DateTime
? ((DateTime)value).ToString("yyyy-MM-ddTHH:mm:ssZ")
: value.ToString();
+
+ //Added additional routine for when value is Byte[] ---bnewbold 22OCT2014
+ if ((value as byte[]) != null)
+ {
+ //When value is passed in a byte array, as when uploading a filestream file, we need to read the value in rather than cast it to a string.
+ byte[] byteArray = (byte[])value; //Cast value as byte array into temp variable
+ writer.WriteStartElement(info.GetName()); //Not using WriteElementsString so need to preface with the XML Tag
+ writer.WriteBase64(byteArray, 0, byteArray.Length); //Just use base64 XML Writer
+ writer.WriteEndElement(); //Close the xml tag
+ continue;
+ }
+
writer.WriteElementString(info.GetName(), SalesforceNamespaces.SObject, xmlValue);
}
}
+ internal string ToCsv()
+ {
+ Type type = GetType();
+ TypeAccessor accessor = ObjectHydrator.GetAccessor(type);
+ string[] values = type.GetProperties().Select(x => GetCsvValue(x, accessor)).ToArray();
+
+ return String.Join(",", values);
+ }
+
private string GetCsvValue(PropertyInfo info, TypeAccessor accessor)
{
Type propertyType = info.PropertyType;
diff --git a/SalesforceMagic/LinqProvider/SOQLVisitor.cs b/SalesforceMagic/LinqProvider/SOQLVisitor.cs
index 49af0e9..b59ae38 100644
--- a/SalesforceMagic/LinqProvider/SOQLVisitor.cs
+++ b/SalesforceMagic/LinqProvider/SOQLVisitor.cs
@@ -18,6 +18,14 @@ private static string VisitExpression(Expression expression, bool valueExpressio
{
switch (expression.NodeType)
{
+ case ExpressionType.Add:
+ case ExpressionType.AddChecked:
+ case ExpressionType.Subtract:
+ case ExpressionType.SubtractChecked:
+ case ExpressionType.Multiply:
+ case ExpressionType.MultiplyChecked:
+ LambdaExpression lambda = Expression.Lambda(expression);
+ return VisitConstant(lambda.Compile().DynamicInvoke());
case ExpressionType.Not:
return VisitExpression(Expression.NotEqual(((UnaryExpression)expression).Operand, Expression.Constant(true)));
case ExpressionType.IsTrue:
@@ -61,12 +69,17 @@ private static string VisitBinary(BinaryExpression node, string opr)
private static string VisitConstant(ConstantExpression node)
{
- if (node.Value is string)
- return "'" + node.Value + "'";
- if (node.Value == null)
- return "null";
+ return VisitConstant(node.Value);
+ }
+
+ private static string VisitConstant(object value)
+ {
+ if (value is string)
+ return "'" + value + "'";
- return node.Value.ToString();
+ return value == null
+ ? "null"
+ : value.ToString();
}
private static string VisitLambda(LambdaExpression node)
diff --git a/SalesforceMagic/ORM/ResponseReader.cs b/SalesforceMagic/ORM/ResponseReader.cs
index 613d1d6..5855e80 100644
--- a/SalesforceMagic/ORM/ResponseReader.cs
+++ b/SalesforceMagic/ORM/ResponseReader.cs
@@ -15,6 +15,18 @@ namespace SalesforceMagic.ORM
{
internal static class ResponseReader
{
+ internal static string ReadStringResponse(string name, XmlDocument document)
+ {
+ XmlNode node = GetSingleNamedNodes(document, name);
+ return node != null ? node.InnerText : null;
+ }
+
+ internal static bool ReadBoolResponse(string name, XmlDocument document)
+ {
+ XmlNode node = GetSingleNamedNodes(document, name);
+ return node != null ? Convert.ToBoolean(node.InnerText) : default(bool);
+ }
+
internal static SalesforceResponse ReadSimpleResponse(XmlDocument document)
{
SalesforceResponse response = new SalesforceResponse();
@@ -96,12 +108,29 @@ internal static T[] ReadArrayResponse(XmlDocument document)
{
return (from XmlNode node in GetNamedNodes(document, "records") select ReadSimpleResponse(node, document)).ToArray();
}
+
+
+ public static QueryResult ReadQueryResponse(XmlDocument document)
+ {
+ return new QueryResult
+ {
+ QueryLocator = ReadStringResponse("queryLocator", document),
+ Done = ReadBoolResponse("done", document),
+ Records = (from XmlNode node in GetNamedNodes(document, "records") select ReadSimpleResponse(node, document)).ToArray()
+ };
+ }
private static XmlNodeList GetNamedNodes(XmlDocument document, string name)
{
return document.GetElementsByTagName(name);
}
+ private static XmlNode GetSingleNamedNodes(XmlDocument document, string name)
+ {
+ XmlNodeList list = document.GetElementsByTagName(name);
+ return list.Count == 0 ? null : list[0];
+ }
+
private static XElement[] GetNamedNodes(XmlNode node, string name)
{
XDocument document = XDocument.Parse(node.OuterXml);
diff --git a/SalesforceMagic/Properties/AssemblyInfo.cs b/SalesforceMagic/Properties/AssemblyInfo.cs
index 0a962c8..399109b 100644
--- a/SalesforceMagic/Properties/AssemblyInfo.cs
+++ b/SalesforceMagic/Properties/AssemblyInfo.cs
@@ -32,6 +32,6 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.2.0.5")]
-[assembly: AssemblyFileVersion("0.2.0.5")]
+[assembly: AssemblyVersion("0.2.1.0")]
+[assembly: AssemblyFileVersion("0.2.1.0")]
diff --git a/SalesforceMagic/SalesforceClient.cs b/SalesforceMagic/SalesforceClient.cs
index 12ec887..529c050 100644
--- a/SalesforceMagic/SalesforceClient.cs
+++ b/SalesforceMagic/SalesforceClient.cs
@@ -94,17 +94,107 @@ public SalesforceSession Login()
}
}
+ ///
+ /// Simple Query
+ /// - Query items based on generic object
+ /// - Generate query using predicate
+ /// - Limited by 200 records
+ ///
+ ///
+ ///
+ ///
+ ///
public virtual IEnumerable Query(Expression> predicate, int limit = 0) where T : SObject
{
return PerformArrayRequest(SoapRequestManager.GetQueryRequest(predicate, limit, Login()));
}
+ ///
+ /// Simple Query
+ /// - Query items based on generic object
+ /// - Limited by 200 records
+ ///
+ ///
+ ///
+ ///
+ public virtual IEnumerable Query(int limit = 0) where T : SObject
+ {
+ return PerformArrayRequest(SoapRequestManager.GetQueryAllRequest(limit, Login()));
+ }
+
+ ///
+ /// Simple Query
+ /// - Query items based on generic object
+ /// - Utilize included raw query
+ /// - Limited by 200 records
+ ///
+ ///
+ ///
+ ///
public virtual IEnumerable Query(string query)
{
// TODO: Validate query
return PerformArrayRequest(SoapRequestManager.GetQueryRequest(query, Login()));
}
+ ///
+ /// Advanced Query
+ /// - Query items based on generic object
+ /// - Returns query locator, and done status which
+ /// can be used to bypass the 200 record limit.
+ ///
+ ///
+ ///
+ ///
+ public QueryResult AdvancedQuery(int limit = 0) where T : SObject
+ {
+ return PerformQueryRequest(SoapRequestManager.GetQueryAllRequest(limit, Login()));
+ }
+
+ ///
+ /// Advanced Query
+ /// - Query items based on generic object
+ /// - Generate query using predicate
+ /// - Returns query locator, and done status which
+ /// can be used to bypass the 200 record limit.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public QueryResult AdvancedQuery(Expression> predicate, int limit = 0) where T : SObject
+ {
+ return PerformQueryRequest(SoapRequestManager.GetQueryRequest(predicate, limit, Login()));
+ }
+
+ ///
+ /// Advanced Query
+ /// - Query items based on generic object
+ /// - Utilize included raw query
+ /// - Returns query locator, and done status which
+ /// can be used to bypass the 200 record limit.
+ ///
+ ///
+ ///
+ ///
+ public QueryResult AdvancedQuery(string query)
+ {
+ return PerformQueryRequest(SoapRequestManager.GetQueryRequest(query, Login()));
+ }
+
+ ///
+ /// Query More
+ /// - Used to retrieve the next set of records
+ /// available in a query using the queryLocator.
+ ///
+ ///
+ ///
+ ///
+ public QueryResult QueryMore(string queryLocator)
+ {
+ return PerformQueryRequest(SoapRequestManager.GetQueryMoreRequest(queryLocator, Login()));
+ }
+
public virtual T QuerySingle(Expression> predicate) where T : SObject
{
return Query(predicate).FirstOrDefault();
@@ -248,6 +338,15 @@ private IEnumerable PerformArrayRequest(HttpRequest request)
}
}
+ private QueryResult PerformQueryRequest(HttpRequest request)
+ {
+ using (HttpClient httpClient = new HttpClient())
+ {
+ XmlDocument response = httpClient.PerformRequest(request);
+ return ResponseReader.ReadQueryResponse(response);
+ }
+ }
+
#endregion
#region Implementation of IDisposable
diff --git a/SalesforceMagic/SalesforceMagic.csproj b/SalesforceMagic/SalesforceMagic.csproj
index ffdce9d..7524fcb 100644
--- a/SalesforceMagic/SalesforceMagic.csproj
+++ b/SalesforceMagic/SalesforceMagic.csproj
@@ -63,6 +63,7 @@
+
@@ -76,6 +77,7 @@
+
diff --git a/SalesforceMagic/SoapApi/RequestTemplates/QueryMoreTemplate.cs b/SalesforceMagic/SoapApi/RequestTemplates/QueryMoreTemplate.cs
new file mode 100644
index 0000000..95fb976
--- /dev/null
+++ b/SalesforceMagic/SoapApi/RequestTemplates/QueryMoreTemplate.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Xml.Serialization;
+using SalesforceMagic.ORM.BaseRequestTemplates;
+
+namespace SalesforceMagic.SoapApi.RequestTemplates
+{
+ [Serializable]
+ public class QueryMoreTemplate
+ {
+ public QueryMoreTemplate()
+ {
+ }
+
+ public QueryMoreTemplate(string queryLocator)
+ {
+ QueryLocator = queryLocator;
+ }
+
+ [XmlElement("queryLocator", Namespace = SalesforceNamespaces.SalesforceRequest)]
+ public string QueryLocator { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/SalesforceMagic/SoapApi/RequestTemplates/XmlBody.cs b/SalesforceMagic/SoapApi/RequestTemplates/XmlBody.cs
index ae97e56..96a6a1f 100644
--- a/SalesforceMagic/SoapApi/RequestTemplates/XmlBody.cs
+++ b/SalesforceMagic/SoapApi/RequestTemplates/XmlBody.cs
@@ -13,6 +13,9 @@ public partial class XmlBody
[XmlElement("query", Namespace = SalesforceNamespaces.SalesforceRequest)]
public QueryTemplate QueryTemplate { get; set; }
+ [XmlElement("queryMore", Namespace = SalesforceNamespaces.SalesforceRequest)]
+ public QueryMoreTemplate QueryMoreTemplate { get; set; }
+
[XmlElement("create", Namespace = SalesforceNamespaces.SalesforceRequest)]
public BasicCrudTemplate InsertTemplate { get; set; }
diff --git a/SalesforceMagic/SoapApi/SoapCommands.cs b/SalesforceMagic/SoapApi/SoapCommands.cs
index 0a8c738..ca4bdb7 100644
--- a/SalesforceMagic/SoapApi/SoapCommands.cs
+++ b/SalesforceMagic/SoapApi/SoapCommands.cs
@@ -32,6 +32,21 @@ internal static string Query(string query, string sessionId)
});
}
+ public static string QueryMore(string queryLocator, string sessionId)
+ {
+ return XmlRequestGenerator.GenerateRequest(new XmlBody
+ {
+ QueryMoreTemplate = new QueryMoreTemplate(queryLocator)
+ },
+ new XmlHeader
+ {
+ SessionHeader = new SessionHeader
+ {
+ SessionId = sessionId
+ }
+ });
+ }
+
public static string CrudOperation(CrudOperation operation, string sessionId) where T : SObject
{
XmlBody body = GetCrudBody(operation);
diff --git a/SalesforceMagic/SoapApi/SoapRequestManager.cs b/SalesforceMagic/SoapApi/SoapRequestManager.cs
index 230947c..15b523c 100644
--- a/SalesforceMagic/SoapApi/SoapRequestManager.cs
+++ b/SalesforceMagic/SoapApi/SoapRequestManager.cs
@@ -32,6 +32,12 @@ public static HttpRequest GetQueryRequest(Expression> predicate
return GetQueryRequest(query, session);
}
+ public static HttpRequest GetQueryAllRequest(int limit, SalesforceSession session) where T : SObject
+ {
+ string query = QueryBuilder.GenerateQuery(limit);
+ return GetQueryRequest(query, session);
+ }
+
public static HttpRequest GetQueryRequest(string query, SalesforceSession session)
{
HttpRequest request = new HttpRequest
@@ -45,6 +51,19 @@ public static HttpRequest GetQueryRequest(string query, SalesforceSession sessio
return request;
}
+ public static HttpRequest GetQueryMoreRequest(string queryLocator, SalesforceSession session)
+ {
+ HttpRequest request = new HttpRequest
+ {
+ Url = session.InstanceUrl + SoapUrl,
+ Body = SoapCommands.QueryMore(queryLocator, session.SessionId),
+ Method = RequestType.POST,
+ };
+ request.Headers.Add("SOAPAction", "queryMore");
+
+ return request;
+ }
+
public static HttpRequest GetCrudRequest(CrudOperation operation, SalesforceSession session) where T : SObject
{
string body = SoapCommands.CrudOperation(operation, session.SessionId);