Skip to content

Commit

Permalink
Implemented queryMore, added better handling of lambdas
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-shattuck committed Nov 19, 2014
1 parent 33cc321 commit 09f475a
Show file tree
Hide file tree
Showing 12 changed files with 325 additions and 18 deletions.
77 changes: 77 additions & 0 deletions SalesforceMagic/Abstract/ISalesforceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,85 @@ public interface ISalesforceClient : IDisposable

#region Query Methods

/// <summary>
/// Simple Query
/// - Query items based on generic object
/// - Limited by 200 records
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="limit"></param>
/// <returns></returns>
IEnumerable<T> Query<T>(int limit = 0) where T : SObject;

/// <summary>
/// Simple Query
/// - Query items based on generic object
/// - Generate query using predicate
/// - Limited by 200 records
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="predicate"></param>
/// <param name="limit"></param>
/// <returns></returns>
IEnumerable<T> Query<T>(Expression<Func<T, bool>> predicate, int limit = 0) where T : SObject;

/// <summary>
/// Simple Query
/// - Query items based on generic object
/// - Utilize included raw query
/// - Limited by 200 records
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="query"></param>
/// <returns></returns>
IEnumerable<T> Query<T>(string query);

/// <summary>
/// Advanced Query
/// - Query items based on generic object
/// - Returns query locator, and done status which
/// can be used to bypass the 200 record limit.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="limit"></param>
/// <returns></returns>
QueryResult<T> AdvancedQuery<T>(int limit = 0) where T : SObject;

/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="predicate"></param>
/// <param name="limit"></param>
/// <returns></returns>
QueryResult<T> AdvancedQuery<T>(Expression<Func<T, bool>> predicate, int limit = 0) where T : SObject;

/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="query"></param>
/// <returns></returns>
QueryResult<T> AdvancedQuery<T>(string query);

/// <summary>
/// Query More
/// - Used to retrieve the next set of records
/// available in a query using the queryLocator.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="queryLocator"></param>
/// <returns></returns>
QueryResult<T> QueryMore<T>(string queryLocator);

T QuerySingle<T>(Expression<Func<T, bool>> predicate) where T : SObject;
T QuerySingle<T>(string query);

Expand Down
11 changes: 11 additions & 0 deletions SalesforceMagic/Entities/QueryResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Collections.Generic;

namespace SalesforceMagic.Entities
{
public class QueryResult<T>
{
public string QueryLocator { get; set; }
public bool Done { get; set; }
public IEnumerable<T> Records { get; set; }
}
}
39 changes: 28 additions & 11 deletions SalesforceMagic/Entities/sObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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;
Expand Down
23 changes: 18 additions & 5 deletions SalesforceMagic/LinqProvider/SOQLVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
29 changes: 29 additions & 0 deletions SalesforceMagic/ORM/ResponseReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -96,12 +108,29 @@ internal static T[] ReadArrayResponse<T>(XmlDocument document)
{
return (from XmlNode node in GetNamedNodes(document, "records") select ReadSimpleResponse<T>(node, document)).ToArray();
}


public static QueryResult<T> ReadQueryResponse<T>(XmlDocument document)
{
return new QueryResult<T>
{
QueryLocator = ReadStringResponse("queryLocator", document),
Done = ReadBoolResponse("done", document),
Records = (from XmlNode node in GetNamedNodes(document, "records") select ReadSimpleResponse<T>(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);
Expand Down
4 changes: 2 additions & 2 deletions SalesforceMagic/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]

99 changes: 99 additions & 0 deletions SalesforceMagic/SalesforceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,107 @@ public SalesforceSession Login()
}
}

/// <summary>
/// Simple Query
/// - Query items based on generic object
/// - Generate query using predicate
/// - Limited by 200 records
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="predicate"></param>
/// <param name="limit"></param>
/// <returns></returns>
public virtual IEnumerable<T> Query<T>(Expression<Func<T, bool>> predicate, int limit = 0) where T : SObject
{
return PerformArrayRequest<T>(SoapRequestManager.GetQueryRequest(predicate, limit, Login()));
}

/// <summary>
/// Simple Query
/// - Query items based on generic object
/// - Limited by 200 records
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="limit"></param>
/// <returns></returns>
public virtual IEnumerable<T> Query<T>(int limit = 0) where T : SObject
{
return PerformArrayRequest<T>(SoapRequestManager.GetQueryAllRequest<T>(limit, Login()));
}

/// <summary>
/// Simple Query
/// - Query items based on generic object
/// - Utilize included raw query
/// - Limited by 200 records
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="query"></param>
/// <returns></returns>
public virtual IEnumerable<T> Query<T>(string query)
{
// TODO: Validate query
return PerformArrayRequest<T>(SoapRequestManager.GetQueryRequest(query, Login()));
}

/// <summary>
/// Advanced Query
/// - Query items based on generic object
/// - Returns query locator, and done status which
/// can be used to bypass the 200 record limit.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="limit"></param>
/// <returns></returns>
public QueryResult<T> AdvancedQuery<T>(int limit = 0) where T : SObject
{
return PerformQueryRequest<T>(SoapRequestManager.GetQueryAllRequest<T>(limit, Login()));
}

/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="predicate"></param>
/// <param name="limit"></param>
/// <returns></returns>
public QueryResult<T> AdvancedQuery<T>(Expression<Func<T, bool>> predicate, int limit = 0) where T : SObject
{
return PerformQueryRequest<T>(SoapRequestManager.GetQueryRequest(predicate, limit, Login()));
}

/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="query"></param>
/// <returns></returns>
public QueryResult<T> AdvancedQuery<T>(string query)
{
return PerformQueryRequest<T>(SoapRequestManager.GetQueryRequest(query, Login()));
}

/// <summary>
/// Query More
/// - Used to retrieve the next set of records
/// available in a query using the queryLocator.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="queryLocator"></param>
/// <returns></returns>
public QueryResult<T> QueryMore<T>(string queryLocator)
{
return PerformQueryRequest<T>(SoapRequestManager.GetQueryMoreRequest(queryLocator, Login()));
}

public virtual T QuerySingle<T>(Expression<Func<T, bool>> predicate) where T : SObject
{
return Query(predicate).FirstOrDefault();
Expand Down Expand Up @@ -248,6 +338,15 @@ private IEnumerable<T> PerformArrayRequest<T>(HttpRequest request)
}
}

private QueryResult<T> PerformQueryRequest<T>(HttpRequest request)
{
using (HttpClient httpClient = new HttpClient())
{
XmlDocument response = httpClient.PerformRequest(request);
return ResponseReader.ReadQueryResponse<T>(response);
}
}

#endregion

#region Implementation of IDisposable
Expand Down
2 changes: 2 additions & 0 deletions SalesforceMagic/SalesforceMagic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
<Compile Include="Configuration\SalesforceSession.cs" />
<Compile Include="Configuration\SessionStoragePolicy.cs" />
<Compile Include="Entities\Abstract\ISalesforceObject.cs" />
<Compile Include="Entities\QueryResult.cs" />
<Compile Include="Entities\SObject.cs" />
<Compile Include="Exceptions\SalesforceRequestException.cs" />
<Compile Include="Extensions\TypeExtensions.cs" />
Expand All @@ -76,6 +77,7 @@
<Compile Include="ORM\QueryBuilder.cs" />
<Compile Include="ORM\ResponseReader.cs" />
<Compile Include="SoapApi\Enum\CrudOperations.cs" />
<Compile Include="SoapApi\RequestTemplates\QueryMoreTemplate.cs" />
<Compile Include="SoapApi\RequestTemplates\UpsertTemplate.cs" />
<Compile Include="SoapApi\RequestTemplates\DeleteTemplate.cs" />
<Compile Include="SoapApi\RequestTemplates\CrudTemplate.cs" />
Expand Down
Loading

0 comments on commit 09f475a

Please sign in to comment.