Retrieve ContentItems that DO NOT have a certain ContentPart

Topics: Troubleshooting, Writing modules
Feb 25, 2015 at 9:28 PM
I've chosen to assign a certain ContentPart, SuggestionPart, to only some content items of a ContentType Entry, not all of them.

Now I'm having trouble querying for them.

SuggestionPart has a boolean property IsSuggestion. I now want to retrieve all Entry items that either do not have a SuggestionPart attached to them OR that have IsSuggestion set to false, like this:
_contentManager
.Query("Entry")
.Where<SuggestionPartRecord>(rec => rec == null || rec.IsSuggestion == false)
Unfortunately, this code throws a NullReferenceException deep down inside NHibernate:
   NHibernate.Loader.Criteria.CriteriaQueryTranslator.GetEntityName(ICriteria subcriteria, String propertyName) +23
   NHibernate.Loader.Criteria.CriteriaQueryTranslator.GetColumnsUsingProjection(ICriteria subcriteria, String propertyName) +62
   NHibernate.Criterion.CriterionUtil.GetColumnNamesUsingPropertyName(ICriteriaQuery criteriaQuery, ICriteria criteria, String propertyName) +21
   NHibernate.Criterion.NullExpression.ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, IDictionary`2 enabledFilters) +77
   NHibernate.Criterion.Junction.ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, IDictionary`2 enabledFilters) +257
   NHibernate.Loader.Criteria.CriteriaQueryTranslator.GetWhereCondition(IDictionary`2 enabledFilters) +277
   NHibernate.Loader.Criteria.CriteriaJoinWalker..ctor(IOuterJoinLoadable persister, CriteriaQueryTranslator translator, ISessionFactoryImplementor factory, ICriteria criteria, String rootEntityName, IDictionary`2 enabledFilters) +281
   NHibernate.Loader.Criteria.CriteriaLoader..ctor(IOuterJoinLoadable persister, ISessionFactoryImplementor factory, CriteriaImpl rootCriteria, String rootEntityName, IDictionary`2 enabledFilters) +277
   NHibernate.Impl.SessionImpl.List(CriteriaImpl criteria, IList results) +676
   NHibernate.Impl.CriteriaImpl.List(IList results) +62
   NHibernate.Impl.CriteriaImpl.UniqueResult() +56
   Orchard.ContentManagement.DefaultContentQuery.Count() in c:\Projects\discoverize\src\Orchard\ContentManagement\DefaultContentQuery.cs:174
   Orchard.ContentManagement.ContentQuery`1.Orchard.ContentManagement.IContentQuery<T>.Count() in c:\Projects\discoverize\src\Orchard\ContentManagement\DefaultContentQuery.cs:220
The only way out I know myself is to consistently add the SuggestionPart to all Entry content items which allows me to skip the check whether an item actually has that part attached. But it seems overkill to save an additional record for every content item when only a few percent of them actually need it.

Is there another way to write the above query?
Feb 26, 2015 at 5:26 AM
Here, you can do a null check on a record property, but not on the record itself. But, normally, if you've added the "SuggestionPart" to the "Entry" content type, all content items of "Entry" type will have a "SuggestionPart", even if no property has been assigned to this part. So, maybe you only have to test
.Where<SuggestionPartRecord>(rec => rec.IsSuggestion == null || rec.IsSuggestion == false)
Maybe this concerns several content types. If so, you only have to add the "SuggestionPart" to these content types (not the content items), and you can do this. Note: In case of your content types are draftable, in this example I query only published versions
var items = _contentManager.Query(VersionOptions.Published, new string[] { "Type1", "Type2" })
    .Where<SuggestionPartRecord>(rec => rec.IsSuggestion == null || rec.IsSuggestion == false)
    .List()
    .ToList();
Finally, if you really want to add, in the content type list, a content type that doesn't have a "SuggestionPart", then you have to use a Where() clause elsewhere. Here, you work on an IContentQuery and the Where() clause is applied on this IContentQuery. Normally, at the end you will use a .Slice() or a .List() to get an IEnumerable of ContentItem. This is on this IEnumerable, just after the List(), that you can do a Where() clause (here a "Linq" expression) to fit your needs. But, by doing that we don't limit the first database results. So, my advice is to use a few content types, or better add a "SuggestionPart" to all of these content types (not the content items)
var items = _contentManager.Query(VersionOptions.Published, new string[] { "Type1", "Type2" })
    .List()
    .Where(x => {
        var part = x.As<SuggestionPart>();
        return part == null || part.IsSuggestion == null || part.IsSuggestion ==false;
    })
    .ToList();
Best,