问题
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>());
回答1:
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">
<a href="@Url.ItemDisplayUrl((IContent)Model.ContentPart)"><img src="@Display.ResizeMediaUrl(Path: firstImage.MediaUrl, Width: 132)" class="main" alt="@title"/></a>
</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.
来源:https://stackoverflow.com/questions/23806766/eager-loading-a-field