Apparently your sequence of AuditLogs
is an IQueryable
, as opposed to an IEnumerable
.
The cause of the problem
You have to be aware about the differences between IEnumerable
and IQueryable
. An object that implements IEnumerable
contains everything to enumerate the sequence: you can try to get the first element, and once you've got an element, you can try to get the next element. The object might need to call some functions to give your the next requested element.
Enumerating at its lowest level is done using GetEnumerator
, MoveNext
and Current
. At a higher level you'll see foreach
, or LINQ functions like ToList
, ToDictionary
, Count
, Any
, Max
: all LINQ functions that don't return IEnumerable<...>
Although an IQueryable seems similar to an IEnumerable, internally it works differently. An object that implements IQueryable
holds an Expression
and a Provider
. The Expression
is a generic representation of what must be queried. The Provider
knows who must execute the query (usually a database management system) and which language this DBMS uses (usually something like SQL).
When you start enumerating (GetEnumerator), the Expression is sent to the Provider, who will translate it into the language that the executor understands (SQL) and executed the query. The returned data is converted to an IEnumerator which is returned to you to MoveNext
The executor does not know your local functions, and thus can't use any of them. In fact, although the creators of entity framework did a marvelous job to translate expressions into SQL, there are a lot of LINQ functions that cannot be used in a query. See Supported and Unsupported LINQ Methods (linq to entities)
By now you should be able to understand why you can't use ToString()
.
Back To Your question
There are several problems though with your searchstring
. If it equals 11
, does that mean Month 11? or does it mean the 11th day of a Month, or do you also want to find the dates in year 2011
? And do you also want to support "2010/11/30" as well as "30/11/2010"?
Consider converting your ChangeTime value into portions for Year / Month / Day, using SqlFunctions.DateName. With this function you can convert the string representation of year, month, day, etc from a DateTime:
var result = auditLogs.Where(auditLog => ...
|| SqlFunctions.DateName("year", auditLog.ChangeTime).Contains(searchParameter)
|| SqlFunctions.DateName("month", auditLog.ChangeTime).Contains(searchParameter)
|| SqlFunctions.DateName("day", auditLog.ChangeTime).Contains(searchParameter));
This would give you the following result
searchString yields ChangeTime:
"2010/11/30" all ChangeTimes on day 20101130
"30/11/2020" all ChangeTimes on day 20101130
"11/30/2020" all ChangeTimes on day 20101130
This seems exactly what you want, whether the data is Japanese, European or American.
But what about the following cases:
- "11/2010". Do you only want the dates of November 2010? or also 11 January 2010?
- "11". Every day of November? Every 11th day of every month? Every day of 2011?
This can only be answered if you specify the requirements unambiguously.
If you want a literal search, you could concatenate the extracted year / month / day strings into the string that you want to use to find in your searchstring. However, this will really limit the search possibilities.
Proper operator interface solves these problems
My advise would be to change the operator interface. Use something like combo boxes. This makes sure that you can't specify invalid dates. Using a zero (or empty) value means that you don't want to filter on the zero year / month / day.
An input separated into year / month / day would make it way more easy for operators to understand the syntax of the searchstring. For you the query will be way easier:
static IQueryable<AuditLog> Where(this IQueryable<AuditLog> auditLogs,
int year, int month, int day)
{
// zero value parameters: do not use. so
// 2011 11 00: all days of November 2011
// 2011 00 14: every 14th day of every month of 2011
if (year != 0)
{
if (month != 0)
{
if (day != 0)
{ // use parameters year, month, day
return auditLogs.Where(auditLog =>
auditLog.ChangeTime.Year == year &&
auditLog.ChangeTime.Month == month &&
auditLog.ChangeTime.Day == day);
}
else
{ // use parameters year, month
return auditLogs.Where(auditLog =>
auditLog.ChangeTime.Year == year &&
auditLog.ChangeTime.Month == month);
}
}
else
{ // don't use Month
if (day != 0)
{ // use parameters year, day
return auditLogs.Where(auditLog =>
auditLog.ChangeTime.Year == year &&
auditLog.ChangeTime.Day == day);
}
else
{ // use parameter year
return auditLogs.Where(auditLog => auditLog.ChangeTime.Year;
}
}
}
else
... etc, don't use parameter day
}
Usage:
int year = comboBox.SelectedItem; // or whatever input device you use
int month = comboBox.SelectedItem // make sure you can't select invalid months
int day = comboBox.SelectedItem // make sure you can't select invalid days
// zero value means: ingnore the value
var result = auditLogs
.Where(...)
.Where(year, month, day);