Using date comparison in LINQ when querying SharePoint OData service - linq

ANSWERED: Go below to find my answer to this question.
I am trying to consume SharePoint 2010 OData from an ASP.NET MVC 3 project using LINQ. I created a default project using the ASP.NET MVC 3 project template with the Razor view engine (VS 2010). I added a service reference pointing to my SharePoint 2010 site.
In my HomeController's Index method (this is just a test project), I created a variable to hold the context and set the Credentials property of the context variable to the current default credentials.
A link query like the following works fine and I can use the created variable to access any of the data:
var query = from a in context.Alerts
select a;
This query simply gets all of the announcements from a list called Alerts in the SharePoint site. This list has fields for the Title, Content, Beginning Date, and Expiration Date.
When I change the query to the following, I do not get the expected results:
var query = from a in context.Alerts
where (a.Begins < DateTime.Now)
select a;
This query ignores the time component of the date. For example, if a.Begins contains a datetime from yesterday, the query returns the AlertItem. If on the other hand, a.Begins contains a datetime with the current date (but an earlier time) the comparison returns false (and a.Begins == DateTime.Now returns true).
If I do the following, the second LINQ query works as expected:
var query = (from a in context.Alerts
select a).ToList();
var query2 = from q in query
where (q.Begins < DateTime.Now)
select q;
What am I missing?

For a Linq to SharePoint query that needs to include the time element of the DateTime you can use TimeOfDay.
var next_slots = (from s in dc.HRDates
where
s.StartTime.HasValue &&
s.StartTime.Value.Date == appt.Value.Date &&
s.StartTime.Value.TimeOfDay == appt.Value.TimeOfDay
...

I have not used SharePoint 2010's OData. However, when querying against the SharePoint 2010 object model, the anomaly you posted is a common behavior, re: you must convert the query to a list before you can query the data.
The typical pattern here is to:
var query = someSharePointQuery.ToList();
var results = query.Where(...).First(...);
Seems odd, but this is how SP 2010 seems to work.

Is DateTime.Today getting used in there somewhere? I did some prototyping with LinqPad, and the only way to duplicate your results was if I had the query using "where (a.Begins < DateTime.Today)"
Here's the quick sketch I did of what it sounds like you're describing:
void Main()
{
List<Alerts> alerts = new List<Alerts>();
alerts.Add(new Alerts(DateTime.Now.AddDays(-1)));
alerts.Add(new Alerts(DateTime.Now));
var query = from a in alerts
where (a.Begins < DateTime.Now)
select a;
foreach (var element in query)
{
Console.WriteLine(element.Begins);
}
}
public class Alerts
{
public DateTime Begins {get; set;}
public Alerts(DateTime begins)
{
Begins = begins;
}
}
As I mentioned, the only way to duplicate your described results was if I changed DateTime.Now to DateTime.Today in the where clause. I would look through your code for accidental usages of the wrong DateTime method.
As an aside, I HIGHLY recommend using LinqPad for prototyping your Linq queries... It can save you time by allowing you to quickly iterate over your code and figure out what your trouble spots are. Also, it's very much worth the $50 for intellisense and other premium features.

After piecing together information from a lot of different sources -- none of which dealt with the exact circumstances of the problem that I am having, I've come to the following conclusion:
When querying SharePoint data using the SharePoint Object Model and Collaborative Application Markup Language (CAML), SharePoint by default does not use the time component of DateTime elements when doing comparisons. To tell SharePoint to use the time component, you must include the IncludeTimeValue = 'TRUE' property on the value type as shown here:
<Where>
<Eq>
<FieldRef Name='Begins' />
<Value Type='DateTime' IncludeTimeValue='TRUE'>
2008-03-24T12:00:00Z
</Value>
</Eq>
</Where>
I found several blog posts that referenced a bug in LINQ to SharePoint that caused the generated CAML to be output as:
<Where>
<Eq>
<FieldRef Name='dateTimeField' IncludeTimeValue='TRUE' />
<Value Type='DateTime'>
2008-03-24T12:00:00Z
</Value>
</Eq>
</Where>
Notice that the IncludeTimeValue = 'TRUE' is on the FieldRef element instead of the Value element. Since this is not the right place for that property, it causes all LINQ to SharePoint queries that perform datetime comparisons to only compare on the date component.
Since I am seeing that exact same behavior when using LINQ and WCF Data Services to connect to SharePoint, I can only assume that under the covers LINQ/WCF Data Services is producing the same invalid CAML.
The solution (assuming I still want to use LINQ/WCF Data Services) is to perform two queries (as stated in the original question). The first LINQ query pulls the list data from SharePoint and stores it in a List. The second LINQ query handles the date comparisons to only pull the data I want.
Since in my particular circumstance, I may have many entries in the SharePoint list covering a large time span but will only be interested in entries on a particular day or couple of days, I wanted to find a way not to bring back the entire list in the first query.
What I settled on was doing a <= and >= comparison to get close, and then further limiting that in my second query. So my two queries now become:
DateTime RightNow = DateTime.Now;
var query = (from a in context.Alerts
where (a.Begins <= RightNow) && (a.Expires >= RightNow)
select a).ToList();
var query2 = from q in query
where q.Begins < RightNow) && (a.Expires > RightNow)
select q;
The first LINQ statement will return all the items that I am ultimately interested in; along with a few that I'm not (because it's comparing just the date component of the datetime). The second LINQ statement will further pare that down to just those that I'm interested in.

I can confirm the bug with SharePoint 2010 LINQ to SharePoint not creating the correct CAML (adding IncludeTimeValue='True' to the FieldRef instead of the Value) is fixed by the October 2013 Cumulative Update to SharePoint Foundation 2010. The hotfix can be downloaded from http://technet.microsoft.com/en-us/sharepoint/ff800847.aspx.
The same bug exists also in SharePoint 2013 which I was informed by Microsoft support should be fixed in the December 2013 Cumulative Update to SharePoint Foundation 2013, but I cannot confirm this. I was informed that the fix is also deployed to Office 365, but I cannot confirm this.

Related

Linq to SharePoint broken after converting columns to content types

We're working on a project that uses Linq to SharePoint. The list had several columns. After using SPMetal to make the class, it was imported into VS to access the data context. The Linq queries worked fine.
We went in a different direction, by deleting the list columns and using content types with site columns. OOTB, the add/edit forms work fine. But after updating the class with SPMetal and importing the class into VS for the data context, all the Linq query show as errors. Visual Studio cannot recognize the columns any longer because they don't appear to be there in the data context from the updated class. The columns are in the content types now, instead of the list.
Is there a way to get the content type's columns to export in the class file with SPMetal? Is there another library to import to write Linq to SharePoint queries with lists that have content types? How do you write Linq queries that use content type columns?
So say you have a list called Documents. It has two columns called 'One' and 'Two'. You make your Linq to SP queries just fine:
DataContext dc = new DataContext("http://sharepoint");
var varResults = (from item in dc.Documents
where item.Two == "blah"
orderby item.One descending
select item);
Then you decide you want to use content types with site columns. The above query breaks when you delete columns 'One' and 'Two' from the list. You make site columns and assign them to a content type called 'Master', parent being item. Master has two content types deriving from it called 'CloneA' and 'CloneB'. Since the clone content type's parent is Master, then they automatically get it's site columns. When you assign the content types to the list, the definition looks like:
Column - Content types
Title - Documents, Master, CloneA, CloneB
One - Master, CloneA, CloneB
Two - Master, CloneA, CloneB
The clone content types will later be used for different Information Policies for retention on the Documents list. After breaking the inheritance and setting up the retention policies on the content types, now items can individually set to a content type which will cause the retention (1 day - CloneA, 1 week - CloneB) to kick off.
But the linq to SP queries are still broken. Even though the site columns show up, SPMetal only captures the bases content type for some reason. So to linq, the columns are not really there with the above query. Typing "where item." the 'Two' doesn't even show up. You have to cast it to make it work (probably not explaining it right). So here's the working code:
DataContext dc = new DataContext("http://sharepoint");
var varResults = (from item in dc.Documents.OfType<Master>()
where item.Two == "blah"
orderby item.One descending
select item);
You may be tempted to use
var varResults = (from item in dc.Documents.OfType<DocumentsMaster>()
Unfortunately, that will only return the items that are associated with that content type in the list. So if you want to get items of a certain content type to filter, knock yourself out.

Lightswitch 2013 Linq queries to Get min value

I'm writing a timesheet application (Silverlight) and I'm completely stuck on getting linq queries working. I'm netw to linq and I just read, and did many examples from, a Linq book, including Linq to Objects, linq to SQl and linq to Entities.(I assume, but am not 100% sure that the latter is what Lightswitch uses). I plan to study a LOT more Linq, but just need to get this one query working.
So I have an entity called Items which lists every item in a job and it's serial no
So: Job.ID int, ID int, SerialNo long
I also have a Timesheets entity that contains shift dates, job no and start and end serial no produced
So Job.ID int, ShiftDate date, Shift int, StartNo long, EndNo long
When the user select a job from an autocomplete box, I want to look up the MAX(SerialNo) for that job in the timesheets entity. If that is null (i.e. none have been produced), I want to lookup the MIN(SerialNo) from the Items entity for that job (i.e. what's the first serial no they should produce)
I realize I need a first or default and need to specify the MIN(SerialNo) from Items as a default.
My Timesheet screen uses TimesheetProperty as it's datasource
I tried the following just to get the MAX(SerialNo) from Timesheets entity:
var maxSerialNo =
(from ts in this.DataWorkspace.SQLData.Timesheets
where ts.Job.ID == this.TimesheetProperty.Job.ID
select ts.StartNo).Min();
but I get the following errors:
Instance argument: cannot convert from 'Microsoft.LightSwitch.IDataServiceQueryable' to 'System.Collections.Generic.IEnumerable
'Microsoft.LightSwitch.IDataServiceQueryable' does not contain a definition for 'Min' and the best extension method overload 'System.Linq.Enumerable.Min(System.Collections.Generic.IEnumerable)' has some invalid arguments
I also don't get why I can't use this:
var maxSerialNo = this.DataWorkspace.SQLData.Timesheets.Min(ts => ts.StartNo);
Can anyone point me in the right direction?
Thanks
Mark
IDataServiceQueryable doesn't support full set of LINQ operator like IEnumerable has.
IDataServiceQueryable – This is a LightSwitch-specific type that allows a restricted set of “LINQ-like” operators that are remote-able to the middle-tier and ultimately issued to the database server. This interface is the core of the LightSwitch query programming model. IDataServiceQueryable has a member to execute the query, which returns results that are IEnumerable. [Reference]
Possible solution is, execute your query first to get collection of type IEnumerable by calling .ToList(), then you can call .Min() against the first query result. But that isn't good idea if you have large amount of data, because .ToList() will retrieve all data match the query and do further processing in client side, which is inefficient.
Another way is, change your query using only operators supported by IDataServiceQueryable to avoid retrieving unnecessary data to client. For example, to get minimum StartNo you can try to use orderby descending then get the first data instead of using .Min() operator :
var minStartNo =
(
from ts in this.DataWorkspace.SQLData.Timesheets
where ts.Job.ID == this.TimesheetProperty.Job.ID
orderby ts.StartNo descending select ts
).FirstOrDefault();

Getting strings from LINQ queries in Sharepoint web part?

I'm setting up a Sharepoint Web Part for tracking language translations for our various products. I can create the translations, now I'm trying to re-populate the form controls if someone chooses an item that that already has a translation, ie they select "French-ProductX" and the various texboxes populate with the data for that translation/product.
My LINQ query is pulling the right data (itemname is the product the user has chosen in a dropdown menu).
var query = from translation in Translations
where (Convert.ToString(translation.Name)) == itemname
select translation;
I can bind the query to a grid and see i'm getting the correct data, but I'm stuck after that. There are two fields (to start with) i want to pull, "Description" and "Features" as strings and fill their corresponding asp:TextBoxes.
I tried converting to array, and it still binds correctly, but it isn't indexing the fields, its viewed aa a single item of type Translation.
I tried an IEnumerable query and converting to a dataview but I got casting errors on the WHERE clause I couldn't resolve.
I tried this, but VS says 'myitem" is an unassigned local variable.
var query = from translation in Translations
where (Convert.ToString(translation.Name)) == itemname
select new { myitem = translation.Description, features = translation.Features };
ItemDescription.Text = myitem;
This seems like it should be a simple thing, am I just missing something here?
What's happening is you are getting an IEnumerable of an anonymous type with the properties of myitem and features.
You would need to change the last line to:
ItemDescription.Text = query.First().myitem;

LINQ and Generated sql

suppose my LINQ query is like
var qry = from c in nwEntitiesContext.CategorySet.AsEnumerable()
let products = this.GetProducts().WithCategoryID(c.CategoryID)
select new Model.Category
{
ID = c.CategoryID,
Name = c.CategoryName,
Products = new Model.LazyList<Core.Model.Product>(products)
};
return qry.AsQueryable();
i just want to know what query it will generate at runtime....how to see what query it is generating from VS2010 IDE when we run the code in debug mode....guide me step by step.
There is not much to see here - it will just select all fields from the Category table since you call AsEnumerable thus fetching all the data from the Category table into memory. After that you are in object space. Well, depending on what this.GetProducts() does - and my guess it makes another EF query fetching the results into memory. If that's the case, I would strongly recommend you to post another question with this code and the code of your GetProducts method so that we can take a look and rewrite this in a more optimal way. (Apart from this, you are projecting onto a mapped entity Model.Category which again won't (and should not) work with Linq-to-Entities.)
Before reading into your query I was going to recommend doing something like this:
string sqlQueryString = ((ObjectQuery)qry).ToTraceString();
But that won't work since you are mixing Linq-to-Entities with Linq-to-objects and you will actually have several queries executed in case GetProducts queries EF. You can separate the part with your EF query and see the SQL like this though:
string sqlString = nwEntitiesContext.CategorySet.ToTraceString();
but as I mentioned earlier - that would just select everything from the Categories table.
In your case (unless you rewrite your code in a drastic way), you actually want to see what queries are run against the DB when you execute the code and enumerate the results of the queries. See this question:
exact sql query executed by Entity Framework
Your choices are SQL Server Profiler and Entity Framework Profiler. You can also try out LinqPad, but in general I still recommend you to describe what your queries are doing in more detail (and most probably rewrite them in a more optimal way before proceeding).
Try Linqpad
This will produce SELECT * FROM Categories. Nothing more. Once you call AsEnumerable you are in Linq-to-objects and there is no way to get back to Linq-to-entities (AsQueryable doesn't do that).
If you want to see what query is generated use SQL Profiler or any method described in this article.

Linq stored procedure with dynamic results

So I'm extremely new to Linq in .Net 3.5 and have a question. I use to use a custom class that would handle the following results from a store procedure:
Set 1: ID Name Age
Set 2: ID Address City
Set 3: ID Product Price
With my custom class, I would have received back from the database a single DataSet with 3 DataTables inside of it with columns based on what was returned from the DB.
My question is how to I achive this with LINQ? I'm going to need to hit the database 1 time and return multiple sets with different types of data in it.
Also, how would I use LINQ to return a dynamic amount of sets depending on the parameters (could get 1 set back, could get N amount back)?
I've looked at this article, but didn't find anything explaining multiple sets (just a single set that could be dynamic or a single scalar value and a single set).
Any articles/comments will help.
Thanks
I believe this is what you're looking for
Linq to SQL Stored Procedures with Multiple Results - IMultipleResults
I'm not very familiar with LINQ myself but here is MSDN's site on LINQ Samples that might be able to help you out.
EDIT: I apologize, I somehow missed the title where you mentioned you wanted help using LINQ with Stored Procedures, my below answer does not address that at all and unfortunately I haven't had the need to use sprocs with LINQ so I'm unsure if my below answer will help.
LINQ to SQL is able hydrate multiple sets of data into a object graph while hitting the database once. However, I don't think LINQ is going to achieve what you ultimately want -- which as far as I can tell is a completely dynamic set of data that is defined outside of the query itself. Perhaps I am misunderstanding the question, maybe it would help if you provide some sample code that your existing application is using?
Here is a quick example of how I could hydrate a anonymous type with a single database call, maybe it will help:
var query = from p in db.Products
select new
{
Product = p,
NumberOfOrders = p.Orders.Count(),
LastOrderDate = p.Orders.OrderByDescending().Take(1).Select(o => o.OrderDate),
Orders = p.Orders
};

Resources