Eager loading a field - orchardcms

Is it possible to eager load a field when querying content using the ContentManager?
I'm using the ContentManager to retrieve all content items of a specific content type. The content type has a MediaLibraryPickerField on it which is creating a select n+1 issue when I iterate over the results of the query. I'd like to force this data to be loaded upfront (join on initial query). This seems straightforward for a ContentPart but I can't get it to work for a ContentField. Is this possible or is there another way to avoid the select n+1 issue with fields?
Here's what I've tried but it has not effect:
var myQuery = _contentManager.Query(new[] { "MyContentType" })
.WithQueryHints(new QueryHints().ExpandParts<MediaPart>());
I've also tried expanding the record:
var myQuery = _contentManager.Query(new[] { "MyContentType" })
.WithQueryHints(new QueryHints().ExpandRecords<MediaPartRecord>());

Here's how I fixed the problem for a projection page, but the same method, or something simpler, could be applied in your case.
In an alternate template for the Content shape of the projection page, Content-ProjectionPage.cshtml, I did the following, which creates a lookup for media that items will be able to use later:
// Pre-fetch images
var projectionItems = ((IEnumerable<dynamic>)
((IEnumerable<dynamic>)Model.Content.Items)
.First(i => i.Metadata.Type == "List").Items)
.Select(s => (ContentItem)s.ContentItem);
var mediaLibraryFields = projectionItems
.SelectMany(i => i.Parts.SelectMany(p => p.Fields.Where(f => f is MediaLibraryPickerField)))
.Cast<MediaLibraryPickerField>();
var firstMediaIds = mediaLibraryFields
.Select(f => f.Ids.FirstOrDefault())
.Where(id => id != default(int))
.Distinct()
.ToArray();
var firstMedia = WorkContext.Resolve<IContentManager>()
.GetMany<MediaPart>(firstMediaIds, VersionOptions.Published, QueryHints.Empty);
var mediaCache = Layout.MediaCache == null
? Layout.MediaCache = new Dictionary<int, MediaPart>()
: (Dictionary<int, MediaPart>) Layout.MediaCache;
foreach (var media in firstMedia) {
mediaCache.Add(media.Id, media);
}
In your case, you don't have to do the complicated drilling into shapes to dig out the fields, as you have access to them directly. I had to do that because the view or a shape table provider is unfortunately the easiest place for me to do that.
Then, when I want to display an image, all I have to do is access my lookup and try to get it from there. In my alternate template MediaLibraryPicker.Summary.cshtml, I do this:
var field = (MediaLibraryPickerField)Model.ContentField;
var imageIds = field.Ids;
if (imageIds.Any()) {
var cm = Model.ContentPart.ContentItem.ContentManager as IContentManager;
var title = cm == null || Model.ContentPart == null
? "" : cm.GetItemMetadata(Model.ContentPart).DisplayText;
var mediaCache = Layout.MediaCache as Dictionary<int, MediaPart>;
var firstImage = mediaCache != null
? mediaCache[imageIds.First()]
: cm.Get(imageIds.First()).As<MediaPart>();
<div class="gallery">
<img src="#Display.ResizeMediaUrl(Path: firstImage.MediaUrl, Width: 132)" class="main" alt="#title"/>
</div>
}
I'm only displaying the first image in the field, here, but you could change that where it does f.Ids.FirstOrDefault(). Just do f.Ids instead and replace the Select with a SelectMany. Also change the summary template so it displays all images after looking them up in the same dictionary.
Once I did that, I had no select N+1, and instead got a single SQL query for all the images on the page.

Related

NetSuite SuiteScript Join Saved Search not returning values

I have tried all manner of different ways to get this search to work properly. I even removed the filter to return values that I know the search returns. I then added the filter, and got different project ID's and verified the search (through the UI) did the job correctly.
It's a JOIN type search that should accept one parameter (project id) and return two pieces of data. That's it. Any ideas on why my parameter (filter) is not working the way it should?
var filter = new Array();
filter[0] = new nlobjSearchFilter('entityid', 'job', 'is', int_Project_ID);
var results = nlapiSearchRecord('transaction', 'customsearch_[...]', filter, null);
if ( !isNull(results) && results.length > 0 )
Here is a link to the criteria and results tabs of the search in NetSuite.
Imgur Link
try this to set the filters after loading search .
var filter = mySearch.filters;
var newfilters = [];
var filterss = {};
filterss.name = 'department';
filterss.operator = 'IS';
filterss.values = dep_fil;
filterss.join = 'employee';
filterss.summary = 'max';
newfilters.push(search.createFilter(filterss));
mySearch.filters = newfilters;
var searchResults = mySearch.run().getRange({
start : 0,
end : 500
});
you can get the values by using
searchResult.getValue({
name : 'email',
join : 'employee',
summary : 'max'
});
There are diffrent type of summary like max,sum,group etc I think the summmary is the reason.
note that this is suitescript 2 method.

How to access 'Abbreviation' field of a custom list in NetSuite custom lists

I have a custom list that is used as a matrix option of Inventory item. Its 'Color'. This custom list has an abbreviation column. I am creating a saved search on item and using Color field(join) and trying to access 'Abbreviation' field of color.
Abbreviation on custom list is available on when 'Matrix Option List' is checked.
Can someone please help me achieve this? I tried to do this through script and it seems like we cannot access 'Abbreviation' column through script. I also tried to use script to write a search directly on 'Color' - custom list and get the 'abbreviation' through search columns. It did not work. Is there a way to access 'Abbreviation' from custom lists?
Thanks in Advance
You can access it via suitescript by using the record type "customlist" and the internal id of the list like so:
var rec = nlapiLoadRecord('customlist', 5);
var abbreviation = rec.getLineItemValue('customvalue', 'abbreviation', 1);
nlapiLogExecution('DEBUG', 'abbreviation', abbreviation);
Keep in mind that the third argument of getLineItemValue is the line number, not the internal ID of the item in the list. If you want to find a specifc line item, you may want to use rec.findLineItemValue(group, fldnam, value).
Unfortunately, it doesn't look like this translates to saved searches. The suiteanswer at https://netsuite.custhelp.com/app/answers/detail/a_id/10653 has the following code:
var col = new Array();
col[0] = new nlobjSearchColumn('name');
col[1] = new nlobjSearchColumn('internalid');
var results = nlapiSearchRecord('customlist25', null, null, col);
for ( var i = 0; results != null && i < results.length; i++ )
{
var res = results[i];
var listValue = (res.getValue('name'));
var listID = (res.getValue('internalid'));
nlapiLogExecution('DEBUG', (listValue + ", " + listID));
}
However, whatever part of the application layer translates this into a query doesn't handle the abbreviation field. One thing to keep in mind is that the 'custom list' record is basically a header record, and each individual entry is it's own record that ties to it. You can see some of the underlying structure here, but the takeaway is that you'd need some way to drill-down into the list entries, and the saved search interface doesn't really support it.
I could be wrong, but I don't think there's any way to get it to execute in a saved search as-is. I thought the first part of my answer might help you find a workaround though.
Here is a NetSuite SuiteScript 2.0 search I use to find the internalId for a given abbreviation in a custom list.
/**
* look up the internal id value for an abbreviation in a custom list
* #param string custom_list_name
* #param string abbreviation
* #return int
* */
function lookupNetsuiteCustomListInternalId( custom_list_name, abbreviation ){
var internal_id = -1;
var custom_list_search = search.create({
type: custom_list_name,
columns: [ { name:'internalId' }, { name:'abbreviation' } ]
});
var filters = [];
filters.push(
search.createFilter({
name: 'formulatext',
formula: "{abbreviation}",
operator: search.Operator.IS,
values: abbreviation
})
);
custom_list_search.filters = filters;
var result_set = custom_list_search.run();
var results = result_set.getRange( { start:0, end:1 } );
for( var i in results ){
log.debug( 'found custom list record', results[i] );
internal_id = results[i].getValue( { name:'internalId' } );
}
return internal_id;
}
Currently NetSuite does not allows using join on matrix option field. But as you mentioned, you can use an extra search to get the result, you could first fetch color id from item and then use search.lookupFields as follows
search.lookupFields({ type: MATRIX_COLOR_LIST_ID, id: COLOR_ID, columns: ['abbreviation'] });
Note: Once you have internalid of the color its better to use search.lookupFields rather than creating a new search with search.create.

Using CSOM in C# to Dynamically Load Project Server Columns From Selected Rows

I use CSOM .NET to load task objects from Project Server 2013, and I need to
filter tasks so that only a subset of them is returned, and
load only a subset of task columns, specified at runtime by the user.
I found this post that shows how to load a dynamic set of columns, and it works nicely for my second requirement. However, I cannot figure out a workable LINQ syntax to combine both column selection and row filtering.
In the example below, I need to load only those "rows" for summary tasks (where t.IsSummary is true), and I want to load only the Name, Start, and Finish columns.
The following code from the referenced post loads just the three columns that I need:
foreach (string fieldName in new List<string>(){"Name","Start","Finish"});
{
ctx.Load(ctx.Tasks,c => c.Include(t => t[fieldName]));
}
ctx.ExecuteQuery();
But when I try to combine where() and include() in the only syntax that makes sense to me, I get InvalidQueryExpressionException on second iteration through the foreach loop: "The query expression is not supported."
foreach (string fieldName in new List<string>(){"Name","Start","Finish"});
{
ctx.Load(ctx.Tasks,
c => c.Where(t => t.IsSummary),
c => c.Include(t => t[fieldName])
);
}
ctx.ExecuteQuery();
I get the same error if I reverse the order of where and include clauses. If I pull the where clause outside of the loop over field names and make it a separate Load call, the summary-task row filtering works, but I lose the dynamic selection of tasks fields. There must be a syntax in LINQ for CSOM that meets both requirements. What is the correct syntax to do this type of query?
The following example demonstrates how to apply select and filter operators in SharePoint CSOM API:
var list = ctx.Web.Lists.GetByTitle(listTitle);
var items = list.GetItems(CamlQuery.CreateAllItemsQuery());
var result = ctx.LoadQuery(items.Where(i => (bool)i["IsSummary"]).Include(i => i["Name"], i => i["Start"], i => i["Finish"]));
ctx.ExecuteQuery();
foreach (var item in result)
{
Console.WriteLine(item["Name"]);
}
So, i believe the following expression is supported in Project Server CSOM API:
var result = ctx.LoadQuery(ctx.Tasks.Where(t => (bool)t["IsSummary"]).Include(t => i["Name"], t => t["Start"], t => t["Finish"]));
ctx.ExecuteQuery();
I answered this myself by using expression trees, which let you filter a set of rows and select a set of columns based on parameters that are only known at runtime. In the example below, I simulate finding out at runtime that I need to filter the tasks on the IsSummary column and that I should retrieve only the five columns Id, Name, Start, IsSubProject, and Finish. Here's the code:
using System.Linq.Expressions;
// Input parms discovered at runtime
string filterColumnName = "IsSummary";
List<string> columnNames = new List<string>(
new[] { "Id", "Name", "Start", "IsSubProject", "Finish" });
// Get the client object for the Published Project matching projGuid
ctx.Load(ctx.Projects, c => c.Where(p => p.Id == projGuid));
ctx.ExecuteQuery();
PublishedProject proj = ctx.Projects.Single();
// Compute the expression tree for filtering the task rows
var taskParm = Expression.Parameter(typeof(PublishedTask), "t");
var predicate = Expression.PropertyOrField(taskParm, filterColumnName);
var filterExpression = Expression.Lambda<
Func<PublishedTask, bool>>(predicate, taskParm);
// Dynamically generate expression tree for each column to be included
var colSelectionList = new List<Expression<
Func<PublishedTask, object>>>();
foreach (var colName in columnNames)
{
var fldExpr = Expression.PropertyOrField(taskParm, colName);
var fldAsObjExpr = Expression.Convert(fldExpr, typeof(object));
var colSelectorExpr = Expression.Lambda<
Func<PublishedTask, object>>(fldAsObjExpr, taskParm);
colSelectionList.Add(colSelectorExpr);
}
// Create query using LoadQuery (Load does not work here)
var taskList = ctx.LoadQuery(proj.Tasks
.Where(filterExpression)
.Include(colSelectionList.ToArray())
);
// Execute the query
ctx.ExecuteQuery();
// taskList now contains just the filtered rows and selected columns
I hope this helps someone else who is stuck on using CSOM to do this for Project Server. I found these two references helpful:
At MSDN
and at Second Life of a Hungarian SharePoint Geek
..Jim

Sitecore HOWTO: Search item bucket for items with specific values

I have an item bucket with more then 30 000 items inside. What I need is to quickly search items that have particular field set to particular value, or even better is to make something like SELECT WHERE fieldValue IN (1,2,3,4) statement. Are there any ready solutions?
I searched the web and the only thing I found is "Developer's Guide to Item
Buckets and Search" but there is no code examples.
You need something like this. The Bucket item is an IIndexable so it can be searched using Sitecore 7 search API.
This code snippet below can easily be adapted to meet your needs and it's just a question of modifying the where clause.if you need any further help with the sitecore 7 syntax just write a comment on the QuickStart blog post below and I'll get back to you.
var bucketItem = Sitecore.Context.Database.GetItem(bucketPath);
if (bucketItem != null && BucketManager.IsBucket(bucketItem))
{
using (var searchContext = ContentSearchManager.GetIndex(bucketItem as IIndexable).CreateSearchContext())
{
var result = searchContext.GetQueryable<SearchResultItem().Where(x => x.Name == itemName).FirstOrDefault();
if(result != null)
Context.Item = result.GetItem();
}
}
Further reading on my blog post here:
http://coreblimey.azurewebsites.net/sitecore-7-search-quick-start-guide/
Using Sitecore Content Editor:
Go to the bucket item then In search tab, start typing the following (replace fieldname and value with actual field name and value):
custom:fieldname|value
Then hit enter, you see the result of the query, you can multiple queries at once if you want.
Using Sitecore Content Search API:
using Sitecore.ContentSearch;
using Sitecore.ContentSearch.Linq;
using Sitecore.ContentSearch.SearchTypes;
using Sitecore.ContentSearch.Linq.Utilities
ID bucketItemID = "GUID of your bucket item";
ID templateID = "Guid of your item's template under bucket";
string values = "1,2,3,4,5";
using (var context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
{
var predicate = PredicateBuilder.True<SearchResultItem>();
predicate = PredicateBuilder.And(item => item.TemplateId == new ID(templateID)
&& item.Paths.Contains(bucketItemID));
var innerPredicate = PredicateBuilder.False<SearchResultItem>();
foreach(string val in values.Split(','))
{
innerPredicate = PredicateBuilder.False<SearchResultItem>();
innerPredicate = innerPredicate.Or(item => item["FIELDNAME"] == val);
}
predicate = predicate.And(innerPredicate);
var result = predicate.GetResults();
List<Item> ResultsItems = new List<Item>();
foreach (var hit in result.Hits)
{
Item item = hit.Document.GetItem();
if(item !=null)
{
ResultsItems .Add(item);
}
}
}
The following links can give good start with the Search API:
http://www.fusionworkshop.co.uk/news-and-insight/tech-lab/sitecore-7-search-a-quickstart-guide#.VPw8AC4kWnI
https://www.sitecore.net/learn/blogs/technical-blogs/sitecore-7-development-team/posts/2013/06/sitecore-7-poco-explained.aspx
https://www.sitecore.net/learn/blogs/technical-blogs/sitecore-7-development-team/posts/2013/05/sitecore-7-predicate-builder.aspx
Hope this helps!

ServiceStack.Text: Use Linq and the ConvertAll

Iam using the ServiceStack.Text JsonObject parser to map into my domain model. I basically have anthing working, except when using Linq to filter on ArrayObject and the try to convert it using convertAll. Iam cannot come arround actuall after using link, adding element by element to an JsonArrayObjects list and then pass it.
var tmpList = x.Object("references").ArrayObjects("image").Where(y => y.Get<int>("type") != 1).ToList();
JsonArrayObjects tmpStorage = new JsonArrayObjects();
foreach (var pic in tmpList) {
tmpStorage.Add(pic);
}
if (tmpStorage.Count > 0) {
GalleryPictures = tmpStorage.ConvertAll(RestJsonToModelMapper.jsonToImage);
}
Question:
Is there a more elegant way to get from IEnumarable back to JsonArrayObjects?
Casting will not work, since where copys elements into a list, instead of manipulating the old one, therefor the result is not an downcasted JsonArrayObjects, rather a new List object.
Best
Considering this more elegant is arguable, but I would probably do:
var tmpStorage = new JsonArrayObjects();
tmpList.ForEach(pic => tmpStorage.Add(RestJsonToModelMapper.jsonToImage(pic)));
And if this kind of conversion is used frequently, you may create an extension method:
public static JsonArrayObjects ToJsonArrayObjects(this IEnumerable<JsonObject> pics)
{
var tmpStorage = new JsonArrayObjects();
foreach(var pic in pics)
{
tmpStorage.Add(RestJsonToModelMapper.jsonToImage(pic));
}
return tmpStorage;
}
This way you would end up with simpler consumer code:
var tmpStorage = x.Object("references")
.ArrayObjects("image")
.Where(y => y.Get<int>("type") != 1)
.ToJsonArrayObjects();
Like this?
var pictures = x.Object("references")
.ArrayObjects("image")
.Where(y => y.Get<int>("type") != 1)
.Select(RestJsonToModelMapper.JsonToImage)
.ToList();

Resources