LINQ to Sense/Net operations

From Sense/Net Wiki
Jump to: navigation, search
  •  
  •  
  •  
  •  
  • 100%
  • 6.3
  • Enterprise
  • Community
  • Planned

Overview

LINQ to Sense/Net

Language-Integrated Query (LINQ) provides query capabilities for many types of data store. Sense/Net ECMS contains an implementation of this concept that provides a way to query against the Sense/Net Content Repository. The LINQ to Sense/Net article provides the overview and basics of the implementation of LINQ in Sense/Net ECMS. This article serves as a reference for developers and portal builders about the supported and not supported operations and the way you can and should use them.

Details

When a system provides a LINQ provider for its data store, it can decide which LINQ operations are supported and which are not. LINQ to Sense/Net was designed for querying content stored in the Content Repository so there are a couple of specialities related to the content structure of Sense/Net Content Repository that must be taken into account when using the feature.

To see examples of our LINQ implementation, please visit the LINQ to Sense/Net examples article.

Supported operations

When an operation is supported by the Sense/Net LINQ provider, it means that it will be compiled to a Content Query in the background that will be executed only when the query result collection is enumerated (e.g. by a foreach loop after the declaration of the query). This is called deferred execution.

These are the supported operations in LINQ to Sense/Net:

  • Any
  • AsEnumerable
  • Count
  • First
  • FirstOrDefault
  • OfType
  • OrderBy
  • OrderByDescending
  • Skip
  • Take
  • ThenBy
  • ThenByDescending
  • ToArray
  • ToDictionary
  • ToList
  • Where

Where

Expression with field

As you can see in the following example, you have to cast the value of a field to the appropriate type to be able to use it in an expression:

Content.All.Where(=> (bool)c["IsFolder"] == true);

Expression with a content property

There are a couple of properties on the Content class that you can use this way:

Content.All.Where(=> c.IsFolder == true);

Expression with a ContentHandler property

It is possible to use ContentHandler properties in LINQ expression if you prefer this syntax. Be aware that the following syntax works only if you are sure that every result content will be of the given type. It is advisable to use the OfType operation first.

Content.All.Where(=> !((MyCustomType)c.ContentHandler).BoolProperty);

Nonexisting fields

Even ContentHandler properties (as seen above) are converted to Content Query field expressions. If a given field does not exist, it will cause a runtime error. The following line throws an InvalidOperationException:

var x = Content.All.Where(c => (int)c["UnknownField"] == 42).ToArray();

Skip, Take

For paging scenarios use the following LINQ syntax to collect only a subset of a content set that fulfills a particular condition:

Content.All.Where(=> c.IsFolder == true).Skip(8).Take(5);

This will be compiled to the following Content Query:

IsFolder:yes .TOP:5 .SKIP:8

As you can see above, in case of LINQ to Sense/Net the given order of the Skip and Take operations are irrelevant. Skip is always executed before Take. This is a different behavior than in other LINQ providers.

OrderBy, OrderByDescending, ThenBy, ThenByDescending

The usual ordering operations can be used with fields or properties the following way. All of them will be compiled to the appropriate Content Query SORT or REVERSESORT expression ( see the Query syntax: sorting article).

Content.All.Where(=> c.IsFolder)
        .OrderBy(=> c.Index)
        .ThenByDescending(=> c.Name);

ToArray, ToList, ToDictionary

All of these operations execute the LINQ expression and enumerate the result collection.

OfType

There are cases when you want to create expressions in a strongly typed way. If the property you want to use exists on the Node class (the base ContentHandler), you may use the following syntax:

Content.All.Where(=> c.ContentHandler.Index > 10);

If you have a custom ContentHandler class, it is highly unadvisable to use casting as it may lead to runtime error. Do not use this:

Content.All.Where(=> !((MyCustomType)c.ContentHandler).BoolProperty); // DO NOT use this

Use the OfType method instead:

Content.All.OfType<MyCustomType>().Where(=> c.BoolProperty);

In this case the type filter will be added to the underlying query:

+TypeIs:MyCustomType +BoolProperty:yes

It is important to note that if you use the strongly typed syntax above and the OfType operation, the result collection will contain items of the type you specified instead of Content!

First, FirstOrDefault

These two methods behave as usual: the first one will throw an exception if there is no content in the result set. You may use filters if you want to.

Content.All.Where(=> c.Id > 10).First(); 
Content.All.First(=> c.Id > 10); 
Content.All.Where(=> c.Id < 20).FirstOrDefault(=> c.Id > 10);

In the latter case the two filters will be combined with an AND operator.

Any

Returns true if the result set contains any elements. As this method executes a count only query in the background, it is much more efficient than checking the result set manually.

Content.All.Any(=> c.Name == "Administrator");
Content.All.Where(c => c.Id < 10).Any(=> c.Name == "Administrator");

Count

The Count method is as fast as Lucene can be. In the background a count only query is executed only returning the length of the result set.

Content.All.Where(=> c.Id < 10).Count();
Content.All.Count(=> c.Id < 10);
Content.All.Where(c => c.Id > 2).Count(c => c.Id < 7);

In the latter case the two filters will be combined with an AND operator.

Custom LINQ operations in Sense/Net

There are a couple of Sense/Net-specific LINQ operations that can be used to modify the behavior of the underlying query. The following methods disable or enable the system content and the lifespan filters respectively (see more about them in the Query syntax article):

ISnQueryable<T> EnableAutofilters();
ISnQueryable<T> DisableAutofilters();
ISnQueryable<T> EnableLifespan();
ISnQueryable<T> DisableLifespan();

Operations only through AsEnumerable()

It is possible to use operations that are not supported directly by the Sense/Net LINQ provider in an expression. The only thing you should do is place an AsEnumerable() call in the expression chain before the not supported operation. However this means that in this case the previous parts of the expression will be executed (as a Content Query) and the subsequent parts will be evaluated in memory (by the LINQ to object provider of .Net).

These are the not supported operations in LINQ to Sense/Net:

  • Aggregate
  • All
  • Average
  • Cast
  • Concat
  • Contains
  • DefaultIfEmpty
  • Distinct
  • ElementAt
  • ElementAtOrDefault
  • Empty
  • Except
  • GroupBy
  • Intersect
  • Last
  • LastOrDefault
  • LongCount
  • Max
  • Min
  • Reverse
  • Select
  • SequenceEqual
  • Single
  • SingleOrDefault
  • SkipWhile
  • Sum
  • TakeWhile
  • ToLookup
  • Union
  • Zip

Select

As the underlying Lucene search engine does not support projection (at least the way it is usually used in Sense/Net ECMS). The Select operation will throw a NotSupportedException if you try to use it in the following way:

Content.All.Where(=> c.IsFolder == true).Select(=> c.Name); // INVALID QUERY

As it is described above, you may use the AsEnumerable() method to overcome this problem, gather the results and make the projection in memory.

Content.All.Where(=> c.IsFolder == true).AsEnumerable().Select(=> c.Name);

In a future release we plan to support partial Content loading to make this use case even faster and less memory-consuming.

Not used operations

There are a couple of LINQ operations that has no meaning in Sense/Net ECMS terefore cannot be used in a LINQ to Sense/Net query. If you try to use the following operations, it will throw an exception:

  • GroupJoin
  • Join
  • Range
  • Repeat
  • SelectMany

Helper LINQ methods

The Content and the Node class have a couple of helper methods that can be called in LINQ expressions to make building queries easier. They can be called individually too, however the main purpose of them is being part of a LINQ expression.

On the Content class:

public bool InFolder(string path) {...}
public bool InFolder(Node node) {...}
public bool InFolder(Content content) {...}
public bool InTree(string path) {...}
public bool InTree(Node node) {...}
public bool InTree(Content content) {...}
public bool Type(string contentTypeName) {...}
public bool TypeIs(string contentTypeName) {...}

On the Node class:

public bool InFolder(string path) {...}
public bool InFolder(Node node) {...}
public bool InTree(string path) {...}
public bool InTree(Node node) {...}
public bool Type(string contentTypeName) {...}
public bool TypeIs(string contentTypeName) {...}

Related links