LINQ to Sense/Net
Language-Integrated Query (LINQ) is a set of features that extends powerful query capabilities to the language syntax of C# and Visual Basic. LINQ introduces standard, easily-learned patterns for querying data, and the technology can be extended to support potentially any kind of data store. This is what we created LINQ to Sense/Net for: to give developers and portal builders the well-known intuitive tools for querying content from Sense/Net Content Repository. To read more about the fundamentals of LINQ please proceed to the MSDN atricle.
LINQ as a programming concept provides a broad set of possible features that the different implementations (e.g. LINQ to SQL, LINQ to XML) may or may not support. It depends on the characteristics of the underlying data source what features may be supported. 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.
The Sense/Net Content Repository is not a relational database. Content in the Content Repository are indexed using the Lucene indexing engine to be able to produce search results super fast. The following concepts of a relational engine are not present in Sense/Net ECMS:
- tables, views
- multiple field comparing in queries (e.g. when field1 equals field2)
We have the following structures instead:
- Content with fields
- every Content is stored in one tree
The queriable collections
The queryable collections of Sense/Net ECMS are the following:
- Content.All: the whole Content Repository.
- myContent.Children: the children of one content, meaning only the direct children.
Please note that every children-related feature works only with the Content class. On the ContentHandler (or Node) level we still publish the original IEnumerable children collection. This will be unified in a subsequent release in the near future. We will provide a possibility to query for a whole subtree (every content under a particular content) and related content defined in a reference field also in a future release.
The result of a LINQ query on the collections above will be an ISnQueryable<Content> (by default) that is also an IEnumerable of course. In case you have built a strongly typed expression, the result will be an ISnQueryable<ContentHandlerType>. See the OfType operation for more info about the type of the result collection.
Only indexed fields are queryable using LINQ to Sense/Net. ContentHandler properties are also can be used in a LINQ expression but only if you provide the type that you want to work with (see the OfType operation for more info). If a non-indexed field appears in a LINQ expression it will cause a runtime error.
Fields and constants
The expression (e.g. that is provided in a Where or First clause) can be very complex. However it is fobidden to provide fields on both sides of a comparison - either one of them must be (or must be able to be compiled to) a constant (it does not matter which side is the field and which is the constant on). The reason behind this is that the expression will be compiled to a Lucene query eventually that needs a field and a constant.
Therefore it is forbidden to write queries like this:
Content.All.Where(c => c.Name == c.DisplayName); // INVALID query Content.All.Where(c => c.Index + 5 > 12); // INVALID query
The second one can be rewritten like this:
Content.All.Where(c => c.Index > 7);
The following looks like an operation on a field, but actually it can be converted to a Lucene query (DisplayName:Example*) therefore it is allowed:
Content.All.Where(c => c.DisplayName.StartsWith("Example"));
The EndsWith and Contains methods are also supported.
The point here is that any kind of expression is valid if one side can be compiled to a constant (even from a complex expression involving method calls and arithmetic operators) and the other side to a field. If this is not the case, a NotSupportedException will be thrown at runtime.
Supported filter methods
These methods are compiled to a Lucene term if they are used in a filter expression:
- StartsWith: compiled to suffix query e.g. Content.All.Where(c => c.Name.StartsWith("Car")) will be "Name:car*"
- EndsWith: compiled to prefix query e.g. Content.All.Where(c => c.Name.EndsWith(".jpg")) will be "Name:*.jpg"
- Contains: compiled to wildcard query e.g. Content.All.Where(c => c.Name.Contains("2013")) will be "Name:*2013*"
- Type: e.g. Content.All.Where(c => c.Type("Folder")) is compiled to "Type:folder"
- TypeIs: e.g. Content.All.Where(c => c.TypeIs("Folder")) is compiled to "TypeIs:folder"
- InFolder: e.g. Content.All.Where(c => c.InFolder("/Root/MyFolder")) will be "Path:'/Root/MyFolder'"
- InTree: e.g. Content.All.Where(c => c.InTree("/Root/MyFolder")) will be "InTree:'/Root/MyFolder'"
- IsAssignableFrom: e.g. Content.All.Where(c => typeof(Group).IsAssignableFrom(c.ContentHandler.GetType()) will be "TypeIs:group"
Query expressions or method-based syntax
When you are working with LINQ, you can write a query in two forms: Using a query expression:
IEnumerable<Content> query = from c in Content.All where c.Id > 10 orderby c.Index select c;
Or using lambda expressions with the method-based syntax:
Content.All.Where(c => c.Id > 10).OrderBy(c => c.Index);
The two declarations above are completely identical, they will compile to the same query in the background. You can use the style you prefer in your code. We prefer the latter one as it is easier to connect that type of query to a LINQ to object query using the AsEnumerable() method.
Ordering of LINQ clauses
There is an important thing to note about the ordering of LINQ clauses in Sense/Net ECMS: the order of the Where, Skip, Take methods does not change the behavior of the query. However they must not appear multiple times in the expression. Check the LINQ to Sense/Net operations article on how to implement paging.
The reason behind the popularity of LINQ is productivity. A LINQ query is much more readable and can be constructed a lot easier than a complex 'for' cycle. However this has a drawback: executing LINQ expressions may be slower than any other querying method. This is true in case of LINQ to Sense/Net too: in a performance-critical use case it may be wiser too use a text-based Content Query, or even a directly built Lucene query tree.
Get Content Query text from LINQ expression
Using the GetCompiledQuery method of a LINQ to Sense/Net expression (which is an instance of the ContentSet class) you can extract the final Lucene query from a complex LINQ expression without executing it:
var linqExp = Content.All.Where(...); //complex expression var compiledLinqQuery = linq.GetCompiledQuery();
You may use the result of the above call (converted to text using the ToString() method) as a regular Content Query.
LINQ and OData
When you send OData requests to the portal containing a $filter query option, it will be compiled using the same expression builder as used during the compilation of a LINQ to Sense/Net expression.
- LINQ to Sense/Net operations
- LINQ to Sense/Net examples
- Content Repository
- OData REST API
- Field Indexing