Skip to content

Conversation

@Hikerakashiya
Copy link
Contributor

Added SearchAsync method to be able to execute SOSL queries

Added SearchAsync method to be able to execute SOSL queries
@wadewegner
Copy link
Owner

Nice! I'll pull today and add some unit tests. Thanks for the contribution!

@wadewegner wadewegner merged commit 61dc729 into wadewegner:master Aug 25, 2015
@wadewegner
Copy link
Owner

I made a few changes, added some checks to the query, created models for response, and included a unit test. Please take a look! @dcarroll FYI.

Thanks again for the contribution!

@wadewegner
Copy link
Owner

Oh, here's the commit: 63ebe78

@Hikerakashiya
Copy link
Contributor Author

Awesome! I like the checks to the query. However, how does one specify what the returned deserialization object is? The JSON that is returned from the Search REST API endpoint does not return a key value that packages the deserilized objects like QueryAsync does (Records). I was struggling with this because I noticed the Attributes that were being returned but was not sure of a way to be able to include the Attributes in the returned object as well as the fields that were defined in the supplied query. If I understand the new updates correctly, SearchAsync will now only return a list of SearchResults that do not have any other properties other than the Attributes. Is that right?

@wadewegner
Copy link
Owner

Yes, it'll only have IDs and attributes. Maybe I missed something? I didn't see anything else modeled in the docs nor in the unit test response. Do either you or @dcarroll have an example of a search where we'll get more in the response?

@wadewegner
Copy link
Owner

Incidentally, try the new functional test out and look at the response/objects.

@Hikerakashiya
Copy link
Contributor Author

When I was doing my testing and using Fiddler, all of the fields supplied in the query were returned as well as the Attributes in the JSON response.

For example:

var test = await client.SearchAsync<SalesforceContact>("FIND {617*} in Phone FIELDS RETURNING Contact(Id, FirstName, Lastname, Email, Phone, MobilePhone)");

Response:

[
  {
    "attributes": {
      "type": "Contact",
      "url": "/services/data/v34.0/sobjects/Contact/003j000000ARJStAAP"
    },
    "Id": "003j000000ARJStAAP",
    "FirstName": "John'",
    "LastName": "Aaaaamos",
    "Email": null,
    "Phone": null,
    "MobilePhone": "(617) 666-6666"
  },
  {
    "attributes": {
      "type": "Contact",
      "url": "/services/data/v34.0/sobjects/Contact/003j00000096YLfAAM"
    },
    "Id": "003j00000096YLfAAM",
    "FirstName": "Bob",
    "LastName": "Jones",
    "Email": null,
    "Phone": null,
    "MobilePhone": "(617) 777-7777"
  },
  {
    "attributes": {
      "type": "Contact",
      "url": "/services/data/v34.0/sobjects/Contact/003j0000008WaEVAA0"
    },
    "Id": "003j0000008WaEVAA0",
    "FirstName": "Stella",
    "LastName": "Pavlova",
    "Email": "spavlova@uog.com",
    "Phone": "(212) 842-5500",
    "MobilePhone": "(617) 666-6666"
  }
]

The documentation does not really indicate this.
https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_search.htm?search_text=search
https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_search.htm?search_text=SOSL

That is why I set the return type to List[T], so an object can be supplied to deserilize the fields. If the API returned Attributes and Records we would be able to deserilize the returned objects into the Records object like QueryAsync does.

@dcarroll
Copy link
Contributor

https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_calls_search_searchresult.htm#topic-title

The results not only return the fields requested, it may return different
objects as well. That's one of the main reasons for the "Attributes" return
value.

On Tue, Aug 25, 2015 at 9:21 AM, Jerad Clingerman notifications@github.com
wrote:

When I was doing my testing and using Fiddler, all of the fields supplied
in the query were returned as well as the Attributes in the JSON response.

For example:
var test = await client.SearchAsync("FIND {617*} in Phone FIELDS RETURNING
Contact(Id, FirstName, Lastname, Email, Phone, MobilePhone)");

Response:
[
{
"attributes": {
"type": "Contact",
"url": "/services/data/v34.0/sobjects/Contact/003j000000ARJStAAP"
},
"Id": "003j000000ARJStAAP",
"FirstName": "John'",
"LastName": "Aaaaamos",
"Email": null,
"Phone": null,
"MobilePhone": "(617) 666-6666"
},
{
"attributes": {
"type": "Contact",
"url": "/services/data/v34.0/sobjects/Contact/003j00000096YLfAAM"
},
"Id": "003j00000096YLfAAM",
"FirstName": "Bob",
"LastName": "Jones",
"Email": null,
"Phone": null,
"MobilePhone": "(617) 777-7777"
},
{
"attributes": {
"type": "Contact",
"url": "/services/data/v34.0/sobjects/Contact/003j0000008WaEVAA0"
},
"Id": "003j0000008WaEVAA0",
"FirstName": "Stella",
"LastName": "Pavlova",
"Email": "spavlova@uog.com",
"Phone": "(212) 842-5500",
"MobilePhone": "(617) 666-6666"
}
]

The documentation does not really indicate this.

https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_search.htm?search_text=search

https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_search.htm?search_text=SOSL


Reply to this email directly or view it on GitHub
#137 (comment)
.

Thanks,
Dave

@wadewegner
Copy link
Owner

Bitten again by the lack of consistent API docs. =)

Thanks @Hikerakashiya. I have an idea - let me please around with it, and I'll share it.

@Hikerakashiya
Copy link
Contributor Author

@wadewegner You're welcome! I never trust the docs!

@dcarroll, the link you posted appears to be referring to the SOAP API Search endpoint. However I agree with your assessment that the results not only return the fields requested, but it may return different objects as well. I noticed the Atrributes.Type property is what specifics what kind of Salesforce object the information comes from. To be able to deserialize the results into a list of objects, you would have to account for all of the fields and the attributes. I wish the JSON at least put the returned fields inside a named key value pair so they could actually be serialized. @wadewegner do you have an idea that will be able to deserilize the included static Attributes that are returned and also account for the dynamic fields that are not wrapped in their own JSON array?

*Note: This example is using my original implementation of returning a List[T] that allows the developer to specify the deserialize type and ignores the Attributes unless the object also included the Attributes manually.
Example:

var test = await client.SearchAsync<SalesforceContact>("FIND {617*} in Phone FIELDS RETURNING Lead(Id, FirstName, LastName, Phone, Company), Contact(Id, FirstnName, LastName, Phone, MobilePhone)");

Response:

[
  {
    "attributes": {
      "type": "Lead",
      "url": "/services/data/v34.0/sobjects/Lead/00Qj0000005NyplEAC"
    },
    "Id": "00Qj0000005NyplEAC",
    "FirstName": "John Lead",
    "LastName": "Aaaaamos",
    "Phone": "(617) 666-6666",
    "Company": "Aethna Home Products"
  },
  {
    "attributes": {
      "type": "Lead",
      "url": "/services/data/v34.0/sobjects/Lead/00Qj00000081drqEAA"
    },
    "Id": "00Qj00000081drqEAA",
    "FirstName": "Bobby",
    "LastName": "Ricky",
    "Phone": null,
    "Company": "Bad"
  },
  {
    "attributes": {
      "type": "Contact",
      "url": "/services/data/v34.0/sobjects/Contact/003j000000ARJStAAP"
    },
    "Id": "003j000000ARJStAAP",
    "FirstName": "John'",
    "LastName": "Aaaaamos",
    "Phone": null,
    "MobilePhone": "(617) 666-6666"
  },
  {
    "attributes": {
      "type": "Contact",
      "url": "/services/data/v34.0/sobjects/Contact/003j00000096YLfAAM"
    },
    "Id": "003j00000096YLfAAM",
    "FirstName": "Bob",
    "LastName": "Jones",
    "Phone": null,
    "MobilePhone": "(617) 777-7777"
  },
  {
    "attributes": {
      "type": "Contact",
      "url": "/services/data/v34.0/sobjects/Contact/003j0000008WaEVAA0"
    },
    "Id": "003j0000008WaEVAA0",
    "FirstName": "Stella",
    "LastName": "Pavlova",
    "Phone": "(212) 842-5500",
    "MobilePhone": "(617) 666-6666"
  }
]

@dcarroll
Copy link
Contributor

Yes, and I agree with the inconsistency in the docs. Having been at SFDC
since before REST, I know that our philosophy was to make SOAP and REST
have similar values sent and returned to the APIs. Also, when in doubt,
check out the Workbench https://workbench.developerforce.com/login.php

On Tue, Aug 25, 2015 at 10:01 AM, Jerad Clingerman <notifications@github.com

wrote:

@wadewegner https://github.com/wadewegner You're welcome! I never trust
the docs!

@dcarroll https://github.com/dcarroll, the link you posted appears to
be referring to the SOAP API Search endpoint. However I agree with your
assessment that the results not only return the fields requested, but it
may return different objects as well. I noticed the Atrributes.Type
property is what specifics what kind of Salesforce object the information
comes from. To be able to deserialize the results into a list of objects,
you would have to account for all of the fields and the attributes. I wish
the JSON at least put the returned fields inside a named key value pair so
they could actually be serialized. @wadewegner
https://github.com/wadewegner do you have an idea that will be able to
deserilize the included static Attributes that are returned and also
account for the dynamic fields that are not wrapped in their own JSON array?

Example:
var test = await client.SearchAsync("FIND {617*} in Phone FIELDS RETURNING
Lead(Id, FirstName, LastName, Phone, Company), Contact(Id, FirstnName,
LastName, Phone, MobilePhone)");

Response:
[
{
"attributes": {
"type": "Lead",
"url": "/services/data/v34.0/sobjects/Lead/00Qj0000005NyplEAC"
},
"Id": "00Qj0000005NyplEAC",
"FirstName": "John Lead",
"LastName": "Aaaaamos",
"Phone": "(617) 666-6666",
"Company": "Aethna Home Products"
},
{
"attributes": {
"type": "Lead",
"url": "/services/data/v34.0/sobjects/Lead/00Qj00000081drqEAA"
},
"Id": "00Qj00000081drqEAA",
"FirstName": "Bobby",
"LastName": "Ricky",
"Phone": null,
"Company": "Bad"
},
{
"attributes": {
"type": "Contact",
"url": "/services/data/v34.0/sobjects/Contact/003j000000ARJStAAP"
},
"Id": "003j000000ARJStAAP",
"FirstName": "John'",
"LastName": "Aaaaamos",
"Phone": null,
"MobilePhone": "(617) 666-6666"
},
{
"attributes": {
"type": "Contact",
"url": "/services/data/v34.0/sobjects/Contact/003j00000096YLfAAM"
},
"Id": "003j00000096YLfAAM",
"FirstName": "Bob",
"LastName": "Jones",
"Phone": null,
"MobilePhone": "(617) 777-7777"
},
{
"attributes": {
"type": "Contact",
"url": "/services/data/v34.0/sobjects/Contact/003j0000008WaEVAA0"
},
"Id": "003j0000008WaEVAA0",
"FirstName": "Stella",
"LastName": "Pavlova",
"Phone": "(212) 842-5500",
"MobilePhone": "(617) 666-6666"
}
]


Reply to this email directly or view it on GitHub
#137 (comment)
.

Thanks,
Dave

@wadewegner
Copy link
Owner

These are great examples, @Hikerakashiya.

I haven't found a slick way to do this yet, so I'll revert a bit back to simply using dynamics. I'm disappointed but won't give up finding something better. =)

@Hikerakashiya
Copy link
Contributor Author

@wadewegner Thanks!

I was thinking along the same lines. It looks like a dynamic object will be the only way to account for the varying fields being returned as well as the Attributes that are always returned. I wonder what it would take to get Salesforce update the endpoint to return better structured JSON.

I look forward to using the updated code when you merge! :)

@wadewegner
Copy link
Owner

I've updated and pushed; very similar to your original code. =) I have three tests you can try out. 937bd8b

@noramirkh
Copy link

Hi folks,

I am trying to use SearchAsync and have following syntax:

var aa = await forceClient.SearchAsync("FIND {searchString} RETURNING Case(Id, CaseNumber)");

No matter what object I pass as type (Case) in this case, I see following error:

JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[My.Web.Models.Salesforce.Case]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path 'searchRecords', line 1, position 17.

Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, object existingValue)
Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, bool checkAdditionalContent)
Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType)
Newtonsoft.Json.JsonConvert.DeserializeObject(string value, Type type, JsonSerializerSettings settings)
Newtonsoft.Json.JsonConvert.DeserializeObject(string value, JsonSerializerSettings settings)
Newtonsoft.Json.JsonConvert.DeserializeObject(string value)
Salesforce.Common.JsonHttpClient.HttpGetAsync(Uri uri)
Salesforce.Common.JsonHttpClient.HttpGetAsync(string urlSuffix)
My.Web.Controllers.CasesController.Search(string searchTerm) in CasesController.cs
+
var aa = await forceClient.SearchAsync("FIND {searchString} RETURNING Case(Id, CaseNumber)");

Any hints how to solve this?

@manuelfuchs
Copy link

manuelfuchs commented Jul 9, 2020

@wadewegner Calling GetLatestVersionAsync on the AuthenticationClient instance results in the mentioned error of @noramirkh . This problem does not occur for me, when the default api version is used.

using var ac = new AuthenticationClient();

await authenticationClient.UsernamePasswordAsync(
                    Constants.ConsumerKey,
                    Constants.ConsumerSecret,
                    Constants.User,
                    $"{Constants.Password}{Constants.SecurityToken}");

// problematic call
await authenticationClient.GetLatestVersionAsync();

using var forceClient = new ForceClient(ac.instanceUrl, ac.accessToken, ac.apiVersion);

var searchResult = await forceClient.SearchAsync<dynamic>("FIND {0123456} IN PHONE FIELDS RETURNING Account (AccountNumber, Description, IsDeleted, Name)");

This snippet results in:
Newtonsoft.Json.JsonSerializationException: 'Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[System.Object]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path 'searchRecords', line 1, position 17.'

I digged into the serialization error and saw, that SearchAsync always returns a List<T> asynchronously. The problem is, that the API now returns a JSON object containing the result JSON array, as seen below, resulting in the previously mentioned exception.

{
  "searchRecords": [
    {
      "attributes": {
        "type": "Account",
        "url": "/services/data/v48.0/sobjects/Account/..."
      },
      "AccountNumber": null,
      "Description": null,
      "IsDeleted": false,
      "Name": "..."
    },
    ...
  ]
}

@noramirkh not calling GetLatestVersionAsync and using the default version of v36.0 (default) solved the issue for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants