Sitecore data API, LINQ and language versions - linq

We have a multi-language Sitecore installation and have discovered a small issue when using LINQ queries off of a Sitecore item. The issue is that it seems to ignore the context language / item language versions in the LINQ query (ie. it returns items that don't have a language version matching the current context language).
Here are two LINQ queries; neither of which work as expected:
var items = item.Children.Where(i => i.TemplateName == "Brochure")
.ToList();
var items = item.Children.Where(i => i.TemplateName == "Brochure" && i.Language == Sitecore.Context.Language)
.ToList();
If I change the statement to use Axes and a Sitecore query, it works as expected and items do not return if they do not have the appropriate language version:
var items = (item.Axes.SelectItems("./*[##templatekey='Brochure']") ?? Enumerable.Empty<Item>())
.ToList();
Has anyone got around this using LINQ, or would it be best to convert everything to Sitecore queries for statements like these?

Items have versions and versions exist for a specific language.
The item itself exists without a language.
If you only want the items that have a version in the context language, you need to check if it has versions:
var items = item.Children
.Where(i => i.TemplateName == "Brochure")
.Where(i => i.Versions.Count > 0)
.ToList();

Related

Entity Framework: LINQ to Entities only supports casting EDM primitive or enumeration types

The following query used to run without issues, until a recent update (latest asp.net web application using framework 4.5 instead of 4. EF version 6 didn't change).
var criteria = (from c in context.CruiseCalendar
where c.ShipId == sourceId
&& c.CruiseDayDate >= dateRange.Start
&& c.CruiseDayDate <= dateRange.End
select new
{
Date = c.CruiseDayDate,
DestinationId = c.ListingId
});
var rendezvous = (from r in context.CruiseCalendar
where (criteria.Any(d => d.Date == r.CruiseDayDate
&& d.DestinationId == r.ListingId))
select r).GroupBy(x => x.CruiseDayDate)
.Where(x => x.Count() >= shipCount)
.SelectMany(x => x)
.OrderBy(r => r.CruiseDayDate)
.ToList();
Now I get the error as noted in the title. I've tried attaching a .ToList() to the 'criteria' query, which yields a different exception: Unable to create a constant value of type 'Anonymous type'. Only primitive types or enumeration types are supported in this context.
Is there something wrong in the way I'm trying to query the data? Is there an alternative approach which circumnavigates the need to a) generate a collection of anonymous objects and b) run a query based on these criteria? (I had this down pat in SQL but am trying to avoid that with a pure EF/Linq approach).
By the way, if it's not clear what I'm trying to achieve here's some test data: (for ease of reading I've used ShipName and PortName instead of shipID and PortId
CruiseDayDate / ShipName / PortName
01/July/2014, Nostromo, Miami
01/July/2014, Potemkin, Miami
01/July/2014, Titanic, San Juan
02/July/2014, Nostromo, Freeport
02/July/2014, Potemkin, Labadee
02/July/2014, Titanic, Freeport
Using Nostromo as an input parameter, the first query finds out where this ship will be, and when. (1 July Miami, 2nd July Freeport)
The second query finds out which other ships the Nostromo will 'rendezvous' with. ie 1st July with the Potemkin (Miami), and 2nd July with the Titanic (Freeport).
02/July/2014 Titanic, Freeport

LINQ - Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator

This seems to be about the most generic error I've come across - multiple SO posts about it are all referring to different issues - well here's a new one :)
I get the error above when the following IQueryable is enumerated:
N.B. items is an IQueryable<tblItem> and keywords is a string
items = items.Where(p => p.heading.ToLower().Contains(keywords) ||
p.description.ToLower().Contains(keywords));
This is confusing because, as the error suggests, it should work fine when you use a Contains - does anyone know how to fix this?
If keyword is a collection that supports enumeration, then it should be other way around:
items = items.Where(p => keywords.Contains(p.heading.ToLower()) ||
keywords.Contains(p.description.ToLower()));
If items is IQueryable then the error may be there and nothing to do with your where statement.
Can you try forcing enumeration before adding your where statement?
For example, suppose you are attempting to join an in memory list with a datatable you will get that error when the query is evaluated or enumerated
List<tblCategory> categories = tblCategory.ToList();
IQueryable<tblItem> items = (from r in tblItem
join c in categories on r.CategoryID equals c.Id select r);
// items = items.Where(p => p.heading.ToLower().Contains(keywords) ||
// p.description.ToLower().Contains(keywords));
var firstMatch = items.FirstOrDefault();
// The error will be generated here even if the where is remmed out
SOLVED
Thanks sgmoore for your input - it helped arrive at this solution:
Assgning the IQueryable list to an IEnumerable list, running a ToList on it and THEN using my filters on the list worked great.
IEnumerable<tblItems> temp = items.ToList();
temp = temp.Where(p => p.heading.ToLower().Contains(keywords) ||
p.description.ToLower().Contains(keywords));
items = temp.AsQueryable();

How do I find a collection of nodes in HtmlAgilityPack using linq to xml?

I want to extract information from various websites. I am using HtmlAgilityPack and Linq to XML. So far I have managed to extract the value from a single node in a website by writing:
var q = document.DocumentNode.DescendantNodes()
.Where(n => n.Name == "img" && n.Id == "GraphicalBoard001")
.FirstOrDefault();
But I am really interested in the whole collection of img's that start with "GraphicalBoard". I tried something like:
var q2 = document.DocumentNode.DescendantNodes()
.Where(n => n.Name == "img" && n.Id.Contains("GraphicalBoard"))
.Select...
But it seems that linq doesn't like the Contains-method, since I lose the Select option in intellisense. How can I extract all the img-tags where the Id starts with "GraphicalBoard"?
How can I extract all the img-tags where the Id starts with "GraphicalBoard"?
You had it already, just stop at the call to Where(). The Where() call filters the collection by the items that satisfies the predicate.
Though you should write it so you filter through the img descendants, not all descendants.
var query = doc.DocumentNode.Descendants("img")
.Where(img => img.Id.StartsWith("GraphicalBoard"));

Access a collection via LINQ and set a single member to a new object

I am trying to access a user object in a collection with the id = to users101 and set this to another users.
Controller.MyObject.SingleOrDefault(x => x.Id == "user101") = OtherUser();
Thanks in advance.
You can't do it with one LINQ expression.
Usually LINQ extensions works on enumerables, if MyObject is a collection you first have to find the required item and then overwrite it with the new object (moreover SingleOrDefault() will simply return null if condition is not satisfied).
You should write something like this (exact code depends on what MyObject is):
var item = Controller.MyObject.SingleOrDefault(x => x.Id == "user101");
if (item != null)
Controller.MyObject[Controller.MyObject.IndexOf(item)] = new OtherUser();
Please note that if you do not really need the check performed by SingleOrDefault() you can simplify the code (and avoid the double search performed in SingleOrDefault() and IndexOf()).
If this is "performance critical" maybe it is better to write an ad-hoc implementation that does this task in one single pass.
Try it in two lines:
var objectWithId = Controller.MyObject.SingleOrDefault(x => x.Id == "user101");
(objectWithId as WhateverTypeOfObjectOtherUserIs) = OtherUser();

How to write dynamic Linq2Sql compiled queries?

I'm having performance issues with Linq2Sql compared to raw ADO.NET which has led me down the path of compiled queries. I have got this far so far
public static readonly Func<MyDataContext, WebServices.Search.Parameters, IQueryable<image>>
Compiled_SelectImagesLinq =
CompiledQuery.Compile<MyDataContext, WebServices.Search.Parameters, IQueryable<image>>(
(dc, parameters) => from i in dc.images
join l in dc.links on i.image_id equals l.image_id
join r in dc.resolutions on i.image_id equals r.image_id
where i.image_enabled == true && i.image_rating >= parameters.MinRating
&& i.image_rating <= parameters.MaxRating
select i
);
However I can't figure out how to add the extra optional parameters to the query as I currently do
if (parameters.Country != null)
{
query = query.Where(x => x.image_country_id == parameters.Country);
}
if (parameters.ComponentId != null)
{
query = query.Where(x => x.links.Any(l => l.link_component_id == parameters.ComponentId));
}
etc, etc
I tried writing another function which does
var query = Compiled_SelectImagesLinq(parameters);
and then adding the extra parameters to the query and returning
return query.Distinct().Take(parameters.Results);
Bit this doesn't seem right and returns no results
Have a look at this article. It may not do what you need (especially since you are compiling your queries), but anytime someone mentions Dynamic and Linq in the same sentence, I refer them to this article:
Dynamic LINQ: Using the LINQ Dynamic Query Library
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
You'd have to benchmark your specific query, but often queries must be used 10-20 times before compiled query performance improvements equal the overhead. Also, how are you adding parameters to the where clause?
Additionally, dynamic compiled queries seems a bit of a mismatch. The Dynamic LINQ query library will do what you need but I don't think you'll get the compiled query performance improvement you want.

Resources