How to substract dates to timspan using Linq to EF? - linq

I am woking with an ASP.NET project which use LinQ to EF to communicate with SQL Server. My project is a shopping cart project, it contains some tables:
Customer(Id, Name, Address, JoinDate, ExpireDate)
I want to write a linq expression to select Id, Name, Address, JoinDate, ExpireDate and Period
The period field is ExpireDate-JoinDate.
I searched in stackoverflow about this problem, there is a way to do substract is use DbFuntions.DiffDate, but the DiffDate return int value but i want timespan value.
I tried to convert the returned int value by DbFuntions.DiffDate to TimeSpan using following code:
var customers = from s in _db.Customers select new {s.Id, s.Name, s.Address, s.JoinDate, s.ExpireDate, Period = TimeSpan.FromDays(DbFunctions.DateDiff(s.ExpireDate, s.JoinDate))};
It result error:
LINQ to Entities does not recognize the method 'System.TimeSpan FromDays(Double)' method, and this method cannot be translated into a store expression.
How to resolve this?

If you want to recieve a TimeSpan value, you should simple subtract the two dates, without using any additional methods, like this:
var customers = from s in _db.Customers
select new
{
s.Id,
s.Name,
s.Address,
s.JoinDate,
s.ExpireDate,
Period = s.ExpireDate - s.JoinDate
};

Related

Check for null value in LINQ before performing operation on it

I am using the SharePoint 2010 REST services to extract information from an SP list. One of the conditions is that if a date exists in a record, and the year of that date is this year, then include it. Otherwise, don't.
What I'm concerned about is that I have to use a date cast method on the date field, and if that field is null then the code will fail. Is there any way to check if the value is null BEFORE using the cast?
My current code is here:
Dim results As List(Of CurrentProjectsItem) =
(From items In service.CurrentProjects
Where (items.StatusValue = "Closed" Or CDate(items.DateClosed).Year = Now.Year))
Order By items.Priority
Select items).ToList()
Any help would be appreciated, I don't use LINQ all that often!
You can include that check in your condition. So this:
CDate(items.DateClosed).Year = Now.Year
logically becomes this:
((items.DateClosed Is Not Nothing) And (CDate(items.DateClosed).Year = Now.Year))
(Though I'm curious what type DateClosed is in the first place, if not a Date?)
Edit:
The DateClosed is Date?
In that case it's a Nullable(Of Date) object, which has properties on it for checking this:
((items.DateClosed.HasValue) And (items.DateClosed.Value.Year = Now.Year))
No need for casting.

How do I get a Distinct list to work with EF 4.x DBSet Context and the IEqualityComparer?

I have been trying for hours to get a Distinct to work for my code.
I am using EF 4.3, MVC3, Razor and trying to get a list downto product id and name.
When I run the Sql query against the DB, it's fine.
Sql Query is
SELECT DISTINCT [ProductId]
,[Product_Name]
FROM [dbo].[PRODUCT]
The only other column in that table is a country code so that's why a standard distinct() isn't working.
I have gone as far as creating an IEqualityComparer
Here is code:
public class DistinctProduct : IEqualityComparer<PRODUCT>
{
public bool Equals(PRODUCT x, PRODUCT y)
{
return x.ProductId.Equals(y.ProductId);
}
public int GetHashCode(PRODUCT obj)
{
return obj.ProductId.GetHashCode();
}
}
here is where I called it.
IEqualityComparer<PRODUCT> customComparer = new DistinctProduct();
IEnumerable<PRODUCT> y = db.PRODUCTs.Distinct(customComparer);
But when it hit's that Last line I get an error out of it stating...
LINQ to Entities does not recognize the method 'System.Linq.IQueryable`1[MyService.Models.PRODUCT] Distinct[PRODUCT](System.Linq.IQueryable`1[MyService.Models.PRODUCT], System.Collections.Generic.IEqualityComparer`1[MyService.Models.PRODUCT])' method, and this method cannot be translated into a store expression.
Can anyone tell me what I'm doing wrong?
Thanks,
David
Is there any reason you could just not use a distinct like the following?
var distinctProdcts = (from p in db.PRODUCTs
select new {
ProductId = p.ProductId,
Product_Name = p.ProductName
}).Distinct();
This would remove the country code from the query before you do the distinct.
Entity Framework is trying to translate your query to a SQL query. Obviously it does not know how to translate the IEqualityComparerer. I think the question is whether you want to do the Distinct in the datbase (in which case your client gets only filtered results) or you are OK with bringing all the data to the client and select distinct on the client. If you want the filtering to happen on the database side (which will make your app perform much better) and you want to be able to use different strategies for comparing you can come up with a code that builds distinct criteria on top of your query. If you are fine with bringing your data to the client (note that it can be a lot of data) you should be able just to do (.ToList() will trigger querying the database and materializing results):
IEnumerable<PRODUCT> y = db.PRODUCTs.ToList().Distinct(customComparer);

Why does DataGrid call Linq query when scrolling?

I am just getting started with Linq, WPF and Silverlight. I am trying to display data which originates from a XML document in a DataGrid. I use a Linq query to select the objects I want and link the result to the DataGrid.
XDocument doc = GedView.GedcomConverter.ConvertToXml(new StreamReader(e.Result));
var query = from person in doc.Descendants("INDI")
select new PersonInfo()
{
Id = (string)person.Attribute("Value"),
GedcomName = (string)person.Descendants("NAME").SingleOrDefault().Attribute("Value"),
Sex = (string)person.Descendants("SEX").SingleOrDefault().Attribute("Value"),
BirthDate = GedcomConverter.ConvertDate(person.Descendants("BIRT").SingleOrDefault()),
DeathDate = GedcomConverter.ConvertDate(person.Descendants("DEAT").SingleOrDefault()),
BurialDate = GedcomConverter.ConvertDate(person.Descendants("BURI").SingleOrDefault()),
};
DataGrid.ItemsSource = query;
DataGrid.SelectedIndex = -1;
However, when the grid is scrolled, the performance is bad. I notice that the ConvertDate method is called many times. (The ConvertDate method converts a human-readable date string into a DateTime? object.)
Why is this? I had assumed that the 'query' would be executed once and not continuously.
What would be the right way to do this? I am using a query because I will want to add some kind of filter to restrict the items in the list at a later date.
Thanks
Try:-
DataGrid.ItemsSource = query.ToList();
The DataGrid is not expecting the IEnumerable that it is accessing to cause something very expensive to happen when it gets an enumerator to find items. However by passing the query itself to the DataGrid you cause the query to execute every time data grid calls GetEnumerator.
Since you want to filter at a later date you could simply re-assign the ItemsSource when you change your filter settings.

Aggregate query with Castle ActiveRecord

I'm trying to perform a simple aggregate query that returns the aggregate's result plus an extra column. This post -> Custom query with Castle ActiveRecord had a good example about how to achieve this, but I can't seem to get it to work. It seems that ActiveRecordMediator.ExecuteQuery returns an ArrayList of objects (instead of ArrayList of object[] which is what I would expect). Also if I try to cast it to ICollection I get a run-time error complaining of invalid cast. Code below, any help appreciated (don't want to use hand-written sql).
HqlBasedQuery query = new HqlBasedQuery(typeof(Something), #"select count(1),
p.Name from Something p
where p.SomeDate > :date
order by p.Name
group by p.Name");
query.SetParameter("date", new DateTime(2009, 1, 1));
var results = from summary in
(ICollection<object[]>)ActiveRecordMediator.ExecuteQuery(query)
select new {
Count = (int)summary[0], Name= (string)summary[1]
};
The line after "from summary in" is the one that throws the invalid cast exception.
(Forgot to mention: using VS2008, .NET 3.5SP1, ActiveRecord 1.0RC3, NHibernate 1.2)
I think you meant count(*) instead of count(1) (this is why you're getting only 1-col rows)
ActiveRecordMediator.ExecuteQuery (at least in RC3) returns an ArrayList (not a generic ICollection) of object[]
Be careful casting count results as int. Some databases return counts as long (e.g. SQL Server)

LINQ - Distinct() returning a different value if extension method used

I have a LINQ query which is attempting to get all of the distinct months of all of the dates in a table.
I had this working using the Distinct() extension method. I then made it more readable by using an extension method to extract the month. And then it stopped returning Distinct results.
Can anyone help me work out what happened here?
As an aside, if someone can tell me the best way to get the distinct months, that would be nice too. But it's more important that I understand why this is failing.
Here's the code.
static class DcUtils
{
public static DateTime GetMonth(this Timesheet_Entry entry)
{
DateTime dt = new DateTime(
entry.Entry_Start_DateTime.Year,
entry.Entry_Start_DateTime.Month,
1
);
return dt;
}
}
public class Demo
{
public DemonstrateBug()
{
TimesheetDataClassesDataContext dc = new TimesheetDataClassesDataContext();
/////////////////////////////////
//// Here are the queries and their behaviours
var q1 = (
from ts
in dc.Timesheet_Entries
select new DateTime(ts.Entry_Start_DateTime.Year, ts.Entry_Start_DateTime.Month, 1)
).Distinct();
// This returns 3 (which is what I want)
int lengthQuery1 = q1.Count();
// And now for the bug!
var q2 = (
from ts
in dc.Timesheet_Entries
select ts.GetMonth()
).Distinct();
// This returns 236 (WTF?)
int lengthQuery2 = q2.Count();
}
}
LINQ to SQL is smart enough to convert the new DateTime() expression from your initial lambda expression into a SQL statements that can be executed at the server. If you replace this expression with an (extension) method, LINQ to SQL will only see a call to an opaque method it knows nothing about, hence it cannot generate any SQL for the method call and the part of the SQL query messing with the dates disappears.
But this shouldn't break anything - what cannot be transformed into SQL must be executed at the client. So what happens? The date you want to perform the distinct operation on cannot be calculated at the server because of the opaque method call, hence the distinct operation cannot be performed at the server, too. But the query you recorded from the broken version contains a DISTINCT statement.
I don't use the LINQ query syntax, but I assume you have written something you don't actually mean or the compiler or LINQ to SQL inferred something you didn't mean.
context
.Timesheet_Entries
.Select(tse => tse.GetMonth())
.Distinct()
versus
context
.Timesheet_Entries
.Distinct()
.Select(tse => tse.GetMonth())
So I guess you got the second one for what ever reason - the distinct operation seems to get propagated over the select. Maybe it's the combination of Distinct() with the query syntax and the contained opaque method call. Try both versions without the query syntax and see what you get back and what queries are send to the server. You can also try to insert ToList() calls to force the transition from LINQ to SQL to LINQ to Objects - this might help to cast some light onto the situation, too.
It occurred to me to run this through the SQL Server Profiler.
This query:
var q1 = (
from ts
in dc.Timesheet_Entries
select new DateTime(ts.Entry_Start_DateTime.Year,
ts.Entry_Start_DateTime.Month,
1)
).Distinct();
generates the following SQL. As you can see, it converted the System.DateTime calls into Transact SQL.
SELECT DISTINCT
[t1].[value]
FROM (
SELECT
CONVERT(
DATETIME,
CONVERT(
NCHAR(2),
DATEPART(
Month,
[t0].[Entry_Start_DateTime]
)
)
+ (''/'' + (CONVERT(NCHAR(2), #p0)
+ (''/'' + CONVERT(NCHAR(4),
DATEPART(
Year,
[t0].[Entry_Start_DateTime]
)
)
))), 101
) AS [value]
FROM [dbo].[Timesheet_Entry] AS [t0]
) AS [t1]
But if I put the month extraction logic in the extension method:
var q2 = (
from ts
in dc.Timesheet_Entries
select ts.GetMonth()
).Distinct();
It generats the following SQL.
SELECT DISTINCT
[t0].[Timesheet_Entry_ID],
[t0].[Entry_Start_DateTime],
[t0].[Entry_End_DateTime],
[t0].[Task_Description],
[t0].[Customer_ID]
FROM [dbo].[Timesheet_Entry] AS [t0]
So it's moved the DISTINCT function to the server, but kept the date extraction code until after the DISTINCT operation, which is not what I want, and is not what happens in the first example.
I don't know if I should call this a bug or a leaky abstraction.

Resources