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);