Dynamic Search using Suitetalk - c#

I am trying to create a C# application (Using suitetalk) that would allow us to search through Netsuite records. The record type will be specified dynamically. Please can you help?
I have checked the webservices and identified that SearchRecord class has many sub classes of type AccountSearch, ItemSearch, etc.
However, I wanted to do these searches dynamically.
AccountSearch acc = new AccountSearch();
SearchResult searchresult = new SearchResult();
searchresult = _service.search(acc);
The above code gives me the list of accounts. But, the AccountSearch is hardcoded here.

The piece of code below works.
SearchRecord search;
SearchRecord searchCriteria;
SearchRecordBasic searchBasicCriteria;
if(recordType.equals(RecordType.account)){
search = new AccountSearchAdvanced();
searchCriteria = new AccountSearch();
searchBasicCriteria = new AccountSearchBasic();
//set criteria on searchBasicCriteria
((AccountSearch) searchCriteria).setBasic((AccountSearchBasic) searchBasicCriteria);
((AccountSearchAdvanced) search).setCriteria((AccountSearch) searchCriteria);
}else if(recordType.equals(RecordType.customer)){
search = new CustomerSearchAdvanced();
searchCriteria = new CustomerSearch();
searchBasicCriteria = new CustomerSearchBasic();
//set criteria on searchBasicCriteria
((CustomerSearch) searchCriteria).setBasic((CustomerSearchBasic) searchBasicCriteria);
((CustomerSearchAdvanced) search).setCriteria((CustomerSearch) searchCriteria);
}else{
search = null;
}
if(search != null) _service.search(search);
But I think a better solution would be to create specific methods for each type of search. That way the code is more readable, plus you avoid all that casting. Then you would have to handle the returned RecordList for each specific record type.

Related

Querying product/pricing information against Dynamics 365 tenant using the C# SDK

I am trying to query product related information in a Dynamics 365 tenant (Version 9.2.22101.170) and with Version 9.0.2.46 of the Microsoft.CrmSdk. Mostly I am interested in querying products by the product number to retrieve price information, but later on, I would introduce more parameters. The below is one of the many methods I've tried (I am aware I am projecting only the name for the time being, eventually I would require price information etc):
var cols = new ColumnSet(new String[] { "name" });
QueryByAttribute query = new QueryByAttribute("product");
query.ColumnSet = cols;
query.Attributes.AddRange("productnumber");
query.Values.AddRange("100002");
var results = service.RetrieveMultiple(query);
if (results != null)
{
var entities = results.Entities.ToList();
if (entities != null)
{
var productEnt = (Product)entities.FirstOrDefault();
Console.WriteLine(productEnt.Name);
}
}
This is the error message returned, on the RetrieveMultiple call:
The entity with a name = 'product' with namemapping = 'Logical' was not found in the MetadataCache. MetadataCacheDetails: ProviderType=Dynamic, StandardCache=True, IsLoadedInStagedContext = False, Timestamp=8343791, MinActiveRowVersion=8343791
The same message is returned when calling any other method. It's clear that the issue is not the query, or the columns being returned but the "product".
Sure enough, I am using the below method to get the list of entity names, and the word "Product" does not show up. I think this explains the error message.
public static EntityMetadata[] GetEntities(IOrganizationService organizationService)
{
Dictionary<string, string> attributesData = new Dictionary<string, string>();
RetrieveAllEntitiesRequest metaDataRequest = new RetrieveAllEntitiesRequest();
RetrieveAllEntitiesResponse metaDataResponse = new RetrieveAllEntitiesResponse();
metaDataRequest.EntityFilters = EntityFilters.Entity;
// Execute the request.
metaDataResponse = (RetrieveAllEntitiesResponse)organizationService.Execute(metaDataRequest);
var entities = metaDataResponse.EntityMetadata;
return entities;
}
Is this a permission issue? Do I need to do some extra loading prior to the query? How do you query product/pricing related information in a Dynamics 365 tenant?
I tried searching for related information online, but I was surprised to practically find almost nothing related to Products.
you are using QueryByAttribute rather you should be using QueryExpression
Try below query and it should help.
// Define Condition Values
var query_productnumber = "100002";
// Instantiate QueryExpression query
var query = new QueryExpression("product");
query.Distinct = true;
// Add columns to query.ColumnSet
query.ColumnSet.AddColumns("productid", "name");
// Define filter query.Criteria
query.Criteria.AddCondition("productnumber", ConditionOperator.Equal, query_productnumber);
var results = service.RetrieveMultiple(query);
if (results != null)
{
var entities = results.Entities.ToList();
if (entities != null)
{
var productEnt = (Product)entities.FirstOrDefault();
Console.WriteLine(productEnt.Name);
}
}

How to convert deprecated IMongoQuery to a FilterDefinitionBuilder

I've got a Visual Studio C# project that's using the MongoDB driver v2.0, and I am attempting to update it to use driver v2.3.0.
There's a section of code which builds a list of IMongoQuery entries based on the presence of various search fields, e.g.
var queryList = new List<IMongoQuery>();
if (!string.IsNullOrEmpty(searchField1))
queryList.Add(Query.Matches(sKey1, searchField1));
...
if (!string.IsNullOrEmpty(searchFieldN))
queryList.Add(Query.Matches(sKeyN, searchFieldN));
How do I convert this to the new FilterDefinitionBuilder syntax? I don't see a similar Add() method in its interface.
UPDATE:
Here's what I'm currently doing, and it is UGLY! Please let me know if there's a better way to do this.
var builder = Builders<BsonDocument>.Filter;
FilterDefinition<BsonDocument> filter = null;
// do this for each search field
if (!string.IsNullOrEmpty(searchField1))
{
if (filter == null)
filter = builder.Eq(sKey1, searchField1);
else
filter = filter & builder.Eq(sKey1, searchField1);
}
I know long time passed but just in case anyone else comes here looking for a solution, here is 2.3.12 compatible way
//create a filter definition builder
var fdefb = new FilterDefinitionBuilder<BsonDocument>(); //or FilterDefinitionBuilder<TModel>
//create a list of Filter Definitions
var queryList = new List<FilterDefinition<BsonDocument>>(); //or List<FilterDefinition<TModel>>
// do this for each search field
if (!string.IsNullOrEmpty(searchField1))
{
if (filter == null)
filter = fdefb.Eq(sKey1, BsonValue.Create(searchField1));
else
filter &= fdefb.Eq(sKey1, BsonValue.Create(searchField1));
}

NetSuite SuiteTalk Joined Search on Custom Objects

I have two custom objects. The parent object is an Order, and it has a list of type Part associated with it. I'm trying to get the list of Parts with a value "Inventory Item" in one field, but only for a particular Order.
I have the name and numeric internalId of the Order available,but the problem is those aren't (as far as I can tell) searchable "fields", there is no "internalId" to provide to tell it to search on one of those.
In the code below, if I don't include the attempt to search on the Order it works fine. I can get all the Parts with a type of "Inventory Item".
I think I need to use a joined search? My attempt at it is below. This produces a SOAP error at runtime:
Additional information: org.xml.sax.SAXException: 'customizationRef' on
{urn:common_2015_1.platform.webservices.netsuite.com}CustomSearchJoin is
required
Code
private static SearchResult NetSuiteSearchOrderItems(NetSuiteService netSuiteService, string order_name, string order_internalId)
{
CustomRecordSearch searchParts = new CustomRecordSearch();
CustomRecordSearchBasic basicRecordSearchParts = new CustomRecordSearchBasic();
basicRecordSearchParts.recType = new RecordRef { internalId = "64" }; //"Order Item" NetSuite Type ID
//adding a search filter on a String Custom Field
SearchStringCustomField partTypeFieldSearch = new SearchStringCustomField();
partTypeFieldSearch.#operator = SearchStringFieldOperator.#is;
partTypeFieldSearch.internalId = "896"; //ID of the Part object's "Type" field
partTypeFieldSearch.operatorSpecified = true;
partTypeFieldSearch.searchValue = "Inventory Item"; //only want inventory item parts
//Show me only parts on a certain Order
CustomSearchJoin orderSearchJoin = new CustomSearchJoin();
CustomRecordSearchBasic orderBasicSearch = new CustomRecordSearchBasic();
orderBasicSearch.recType = new RecordRef { internalId = "56" }; //"Order" NetSuite Type ID
//-- WHAT GOES HERE INSTEAD OF SearchStringCustomField?
//adding a search filter on a String Custom Field
SearchStringCustomField fsoItemTypeSearch = new SearchStringCustomField();
fsoItemTypeSearch.#operator = SearchStringFieldOperator.#is;
fsoItemTypeSearch.internalId = "name"; //I have the "name" and numeric "internal id" of the order available.
fsoItemTypeSearch.operatorSpecified = true;
fsoItemTypeSearch.searchValue = order_name;//search filter value
orderSearchJoin.searchRecordBasic = orderBasicSearch;
//add in the things we want to search on
basicRecordSearchParts.customFieldList = new SearchCustomField[] { partTypeFieldSearch };
//add the basic search and join search and then perform the search
searchParts.basic = basicRecordSearchParts;
searchParts.customSearchJoin = new CustomSearchJoin[] { orderSearchJoin };
SearchResult response = netSuiteService.search(searchParts);
return response;
}

How Lucene.net filter works

i got a piece of code to add filter with Lucene.net but good explanation was not there to understand the code. so here i paste the code for explanation.
List<SearchResults> Searchresults = new List<SearchResults>();
string indexFileLocation = #"C:\o";
Lucene.Net.Store.Directory dir = Lucene.Net.Store.FSDirectory.GetDirectory(indexFileLocation);
string[] searchfields = new string[] { "fname", "lname", "dob", "id"};
IndexSearcher indexSearcher = new IndexSearcher(dir);
Filter fil= new QueryWrapperFilter(new TermQuery( new Term(field, "5/12/1998")));
var hits = indexSearcher.Search(QueryMaker(searchString, searchfields), fil);
for (int i = 0; i < hits.Length(); i++)
{
SearchResults result = new SearchResults();
result.fname = hits.Doc(i).GetField("fname").StringValue();
result.lname = hits.Doc(i).GetField("lname").StringValue();
result.dob = hits.Doc(i).GetField("dob").StringValue();
result.id = hits.Doc(i).GetField("id").StringValue();
Searchresults.Add(result);
}
i need explanation for the below two line
Filter fil= new QueryWrapperFilter(new TermQuery( new Term(field, "5/12/1998")));
var hits = indexSearcher.Search(QueryMaker(searchString, searchfields), fil);
i just like to know first lucene search & pull all data and after implement filter or from the beginning lucene pull data based on filter? please guide. thanks.
i just like to know first lucene search & pull all data and after implement filter or from the beginning lucene pull data based on filter? please guide. thanks.
Lucene.Net will perform your search AND your filtered query and after it, it will "merge" the result. The reason to do it I believe is to cache the filtered query, because it will be more likely to have a hit on the next time than the search query.

getting user details from AD is slow

Im using the following code to get a bunch of information about employees from specific departments and returning a list from AD...
Whilst it works, it appears to be quite slow, is a there more efficient way of getting various user details from AD?
public static List<Employee> GetEmployeeListForDepartment(string departpment)
{
using (HostingEnvironment.Impersonate())
{
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, domain);
GroupPrincipal gp = GroupPrincipal.FindByIdentity(ctx, departpment);
PrincipalSearchResult<Principal> members = gp.GetMembers();
List<Employee> employees = new List<Employee>();
foreach (var member in members)
{
var emp = CreateEmployee(member);
employees.Add(emp);
}
return employees.OrderBy(x => x.FirstName).ToList();
}
}
private static Employee CreateEmployee(Principal member)
{
if (member != null)
{
DirectoryEntry de = (member.GetUnderlyingObject() as DirectoryEntry);
if (de != null)
{
string mobile = de.Properties.Contains("mobile") ? de.Properties["mobile"][0].ToString() : "";
string title = de.Properties.Contains("description") ? de.Properties["description"][0].ToString() : "";
//ETC ETC...
return new Employee { etc.. };
}
}
return new Employee();
}
Your problem is that you are using System.DirectoryServices.AccountManagement... While I hate saying it, it's sadly the truth. The way AccountManagement works under the hood is that it runs a seperate LDAP query to retrieve each item seperately. So when you iterate through members it's making a seperate call back through LDAP for each member. What you want to do instead is run an LDAP query using System.DirectoryServices.DirectorySearcher.
My assumption is that department is a group, based on how you are using it. Here is how I would do it. (my code is in VB.Net... sorry). Make sure to get the fully qualified DN for your group, or look it up in advance and plug it into the query.
Dim results = LDAPQuery("(memberOf=CN=Fully,OU=Qualified,DC=Group,DC=Distinguished,DC=Name)", New String() {"mobile", "description"})
Dim emps = (from c as System.DirectoryServices.SearchResult in results _
Select New Employee() {.Name = c.Properties("description"), .Mobile = c.Properties("mobile")}).ToList()
Public Function LDAPQuery(ByVal query As String, ByVal attributes As String()) As SearchResultCollection
'create directory searcher from CurrentADContext (no special security available)
Dim ds As New DirectorySearcher(System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain().GetDirectoryEntry())
ds.PageSize = 1000
'set filter string
ds.Filter = query
'specify properties to retrieve
ds.PropertiesToLoad.AddRange(attributes)
'get results
Dim results As SearchResultCollection = ds.FindAll()
Return results
End Function
You should be able to use the Active Directory API directly.
Most of it is available under 'System.DirectoryServices'
I don't have any code to hand to do exactly what you need, but I do have an article on my Blog from about a year back, that shows how to create local user accounts in AD, all of which uses the same assemblies as needed to get user information.
http://shawtyds.wordpress.com/2010/12/08/a-little-bit-of-ldap-here-there/
NOTE: AD however by it's very nature is slow, so there's a good chance you might not be able to get any faster if you have a large directory.

Categories