In my ASP.NET MVC3 application I have the following method that use EF entities:
public IQueryable<Products> GetCreatedProducts(int year)
{
return GetAllProducts().Where(m => Equals(DateUtilities.ExtractYear(m.ProductCreationDate), year));
}
ProductCreationDate is a field stored in the database as string "YYYYMMddhhmm".
int DateUtilities.ExtractYear(string date) is a method that I created in order to get the year from the string. Similarly I use ExtractMonth and ExtractDay.
However when I execute my application it launches an NotSupported exception:
"LINQ to Entities does not recognize the method 'Int32 ExtractYear(System.String)' method, and this method cannot be translated into a store expression."
By googling the problem I found out that it is a bug in LINQ to Entities Anybody knows a workaround?
My first question would be why are you storing a date in the database as a string? Store it as a date and use data comparison against it through Linq.
The issue that you're coming up against is Linq doesn't understand what the method ExtractYear is when it tries to compile your Linq statement to raw SQL
EDIT
Another alternative would be to create a view in the database and query against the view with a computed column that represents the string value as a datetime.
In this case you could go the other way:
GetAllProducts().Where(m => m.ProductCreationDate.StartsWith(year.ToString()));
Rather than extract the year from the string, find all strings beginning with the year you're after.
I'm not sure if these will work for the Month and Day searches:
var monthString = String.Format("{0:00}", month);
GetAllProducts().Where(m => m.ProductCreationDate.Substring(4,2) == monthString);
var dayString = String.Format("{0:00}", day);
GetAllProducts().Where(m => m.ProductCreationDate.Substring(6,2) == dayString);
I don't think this is really a bug so much as a limitation -- there's no SQL version of ExtractYear, so it's not possible to execute this query.
You'll have to rewrite the query to either use only SQL-compatible commands (like some other answers have shown) or pull lots of data out of the database and do the filtering with LINQ-to-Objects (this could potentially be very expensive, depending on what you're querying and how many of the records match).
You get the exception because you work with IQueryable. Entity Framework will try to translate the predicate in the where clause into SQL but EF doesn't know how to translate your method.
If you don't want to (or can't) change the database you still have a few options:
Pull all rows to the client side and do the filtering on the client. You do this by changing from IQueryable to IEnumerable:
return GetAllProducts()
.AsEnumerable()
.Where(m => Equals(DateUtilities.ExtractYear(m.ProductCreationDate), year))
.AsQueryable();
To be more efficient you can use your own SQL (this requires EF 4.1 and only avoid pulling all products to the client, not the scan on the server):
return context
.Products
.SqlQuery("select * from Products where substring(ProductCreationDate, 1, 4) = '2012'")
.AsQueryable();
Note that I was lazy and hardcoded the SQL. You should probably parametrize it and make sure you avoid any SQL injection attacks.
You could do the extraction inline, something like this:
public IQueryable<Products> GetCreatedProducts(int year)
{
string sYear = year.ToString();
return GetAllProducts().Where(m => m.ProductCreationDate.Substring(0,4) == sYear);
}
Related
I'm storing dates in SQL Server as varchar so they become strings in C# by Entity Framework.
Then I tried to convert then to date but the code throws an exception.
In SQL Server:
CREATE TABLE [dbo].[Achievements]
(
...
[DatePerformance] [VARCHAR](20) NULL,
)
The date is stored in this format : "25/04/2019"
In C#:
public static School_LPEntities db = new School_LPEntities();
I tried:
List<Achievements> res = db.Achievements.Where(x => DateTime.Parse(x.DatePerformance) <= DateTime.Today.Date).ToList();
and:
List<Achievements> res = db.Achievements.Where(x => DateTime.ParseExact(x.DatePerformance, "dd/MM/yyyy",
CultureInfo.InvariantCulture) <= DateTime.Today.Date).ToList();
The code throws:
System.NotSupportedException: 'LINQ to Entities does not recognize the method 'System.DateTime Parse(System.String)' method, and this method cannot be translated into a store expression.'
As others have said in the comments, the number of good reasons to store your dates as varchar's in SQL is zero.
You said:
so they become strings in C# by Entity Framework
Once you get the date in C# you can do this easily anyway with a .ToString().
But... as to your specific error, what it's telling you is that you can't use this method inside of an Entity Framework LINQ statement. It's best to think of LINQ statements as queries. Because, in fact, it needs to be able to convert your LINQ statement into an actual SQL query. You can't use DateTime.Parse() inside of a SQL query, so it throws this error.
If you really MUST store your dates as varchar's in the database (and again, this is a terrible idea) then I'm not seeing a really simple solution. Off hand, I would tell you to first, create your list, then loop through it:
List<Achievements> res = db.Achievements.ToList();
Then:
foreach (var achievement in res)
{
if (DateTime.Parse(achievement.DatePerformance) <= DateTime.Today.Date)
{
// do something here
}
}
But this is a really clunky way to work around the real solution: Just store your dates as dates!!!
I'm setting a CreateFilteredQuery with IQueryable Interface. I have to return an IQueryable value but I'm getting IOrderedQueryable through an OrderBy function. Is there any way to copy all sensible data (not format from order and those unnecesary data) with that order into an IQueryable one?
I have tried almost everything. I can't change the return value because the functions is also for another "filters".
I'm trying to sort a column that is originally a String Column but is filled with numbers, so I have to parse into Int and then, orderby. (If I don't do that, if I order the result will be like: 1 2 20 22 3 instead of 1 2 3 20 22)
I created "tareas" with base.CreateFilteredQuery(input), and I'm trying to order by externalId before parsing to int
if (input.Sorting == "externalId asc")
{
var tareasOrdenadas = (tareas.ToList()).OrderBy(t => int.Parse(t.ExternalId));
return tareasOrdenadas;
}
I expect the output of System.Data.Entity.DbSet or IQueryable. Until this moment I had System.Linq.OrdenedEnumerable or just simply IOrderedEnumerable
PD: When I modify "tareas" in other filters, I have a "System.Data.Entity.Infrastructure.DbQuery value for System.Linq.IQueryable type"
I need an IQueryable type, not an IOrderedEnumerable, AsQueryable() doesn't works
PD2: Thanks you all for the help. Sorry about no replies, I was out of the office for a few days. I'll try all you give to me, thanks you all.
Happy Coding
You can call .AsQueryable() on an IEnumerable sequence and it will return IQueryable
You can write a db function in database to convert string to integer(because there isn't any built-in functionality for this) and use that function in you linq query convert that string value to integer when you are ordering. In that way you don't have to call .ToList() method
Something like this
if (input.Sorting == "externalId asc")
{
// you can easily return IOrderedQueryable as IQueryable
retrun tareas.OrderBy(t => context.YourDBFunction(t.ExternalId));
}
I think your root problem is requiring an IQueryable return type. I assume the purpose is to enable adding additional database-level operations (sorting, filtering) onto the base query. The problem here is that IQueryable providers support a limited set of operations, so you often have to escape the database layer (I would use AsEnumerable() for this instead of ToList(), BTW) to apply in-memory operations. Once you do that, there's no way to go back to the database layer.
If you change your expected return type to IEnumerable you should lose the compilation errors.
Another option is to stay in the database layer by using Convert.ToInt32 instead of int.Parse:
if (input.Sorting == "externalId asc")
{
var tareasOrdenadas = tareas.OrderBy(t => Convert.ToInt32(t.ExternalId));
return tareasOrdenadas;
}
i have a win application in c# where i would obtain a linq query with a where condition containing a Data concatened by table fields.
my query is :
var fatture = (from t016 in cont.T016_FATT
where t016.ID_FATTURA = aiIdFattura
&& Convert.toDateTime("01" + t016.COD_MESE+"/"+t016.ANNO) > aiData
select t016).ToList();
where t016.COD_MESE is a string rapresenting month value, aiIdFattura is a decimal parameter passed in function and aiData is a DateTime parameter.
but this linq query gives me this error:
LINQ to Entities does not recognize the method Convert.ToDateTime
i have found in internet a solution suggesting to use DBFunctions that is present in EntityFramework (version 6) but i have a minor version of EntityFramework so i cannot use this function.
another found solution was to format date before linq query but in my case is not possible because depending from T016_FATT so i cannot format my date first.
so have you any idea to suggest me?
thanks a lot
Remember your query is going to be translated to SQL, and in this case your Linq provider doesn't know how to translate that method call to a sql statement. To avoid that issue, you can make the switch of Linq to Entities to Linq to Objects using AsEnumerable extension method. After you call that method you can apply your date conversion:
var fatture = cont.T016_FATT.Where(e=>e.ID_FATTURA == aiIdFattura)
.AsEnumerable()
.Where(e=>Convert.toDateTime("01" +"/"+ e.COD_MESE+"/"+e.ANNO) > aiData)
.ToList();
If you want to see the methods that are supported by Linq to Entities, you can check this link.
I recently found out that i cannot call any methods from within a linq query. I am trying to write a query that, on the where clause compares two byte arrays. The value on the database is a GUID of type Raw(32) and it is returned as a byte array. This is the record ID for this table. I need to compare it to another byte array. the second byte array could be converted to a string but since i cannot call methods from within linq i was unable to compare.
I tied a custom "Compare" method, i also wrote an extension method. All received an error indicating "LINQ to Entities does not recognize the method"
Here is the code for what i am trying to do. The where clause causes this error:
LINQ to Entities does not recognize the method 'Boolean SequenceEqual[Byte] (System.Collections.Generic.IEnumerable1[System.Byte], System.Collections.Generic.IEnumerable1[System.Byte])' method, and this method cannot be translated into a store expression."
EPSGEntities dbContex = new EPSGEntities();
byte[] byteArray = ParseHex(ViewState["itemID"].ToString());
var q = (from d in dbContex.EPSG_VSOREJECTS
where d.SDSRECID.SequenceEqual(byteArray)
select d).First();
What version of EntityFramework are you using? On EF6 I am able to simply do the following against a SQL 2012 table with a varbinary column:
var q = dbContext.EPSG_VSOREJECTS.FirstOrDefault(e => e.SDSRECID == byteArray);
Is the SDSRECID property on EPSGEntities of type byte[]?
The alternative here would be to go to straight Sql to get your object. Something like:
dbContext.Database.SqlQuery<EPSG_VSOREJECT>("SELECT TOP 1 *" +
"FROM dbo.EPSGEntities" +
"WHERE SDSRECID = #byteString",
new SqlParameter
{
ParameterName = "byteString",
Value = ViewState["itemID"].ToString(),
}).FirstOrDefault();
Linq to Entities in EF is awesome for most queries, but I sometimes drop into sql when I need to do something unsupported, complex, or just fast. Hope this helps!
I'm not entirely sure this works, but I've found calling .AsEnumerable() on the IQueryable object set lets me apply pretty much any code I wish:
var q = dbContex.EPSG_VSOREJECTS.
.AsEnumerable()
.Where(d => d.SDSRECID.SequenceEqual(byteArray));
Doing so seems to prevent EF from trying to translate the Where() clause into SQL syntax, but I have no real idea what the performance hit would/will be.
This is also using method syntax, since I'm not real familiar with query syntax. HTH.
EDIT:
As some others have noted, you have to be careful with how you add any of the iterative methods (AsEnumerable(), ToList(), etc.) since past that point you are no longer building SQL against your data store. Once you start iterating, EF will execute whatever query has been built up to that point, and from then on you are filtering the result set from the LINQ query.
In this case, I don't know that this can be avoided, unless someone can build the same query as a sproc (which EF can execute on your behalf).
I have an IQueryable that has a list of pages.
I want to do: Pages.OrderByDescending(o => CalculateSort(o.page));
the method calculate sort is similar to that here is a plain english version:
public int calculatesort(page p)
{
int rating = (from r in db.rating select r). sum();
int comments = //query database for comments;
float timedecayfactor = math.exp(-page.totalhoursago);
return sortscore = (rating +comments)* timedecayfactor;
}
when I run a code similar to the one above an error is thrown that the mothode calculatesort cannot be converted to sql.
How can I do a conver the function above to be understood by sql so that I can use it to sort the pages?
Is this not a good approach for large data? Is there another method used to sort sets of results other than dynamically at the database?
I havent slept for days trying to fix this one :(
your code is nowhere near compiling so I'm guessing a lot here but I hope this gives an idea none the less.
As several have posted you need to give Linq-2-Sql an expression tree. Using query syntax that's what happens (by compiler magic)
from p in pages
let rating = (from r in db.rating
where r.PageId == p.PageId
select r.Value).Sum()
let comments = (from c in db.Comments
where c.PageId == p.PageId
select 1).Count()
let timedecayfactor = Math.Exp(-(p.totalhoursago))
orderby (rating + comments)*timedecayfactor descending
select p;
I haven't actually tried this against a database, there's simply too many unknown based on your code, so there might still be stuff that can't be translated.
The error occurs because LINQ cannot convert custom code/methods into SQL. It can convert only Expression<Func<>> objects into SQL.
In your case, you have a complex logic to do while sorting, so it might make sense to do it using a Stored Procedure, if you want to do it in the DB Layer.
Or load all the objects into main memory, and run the calculate sort method on the objects in memory
EDIT :
I don't have the code, so Describing in english is the best I can do :
Have table with structure capable of temporarily storing all the current users data.
Have a calculated field in the Pages table that holds the value calculated from all the non-user specific fields
Write a stored procedure that uses values from these two sources (temp table and calc field) to actually do the sort.
Delete the temp table as the last part in the stored proc
You can read about stored procs here and here
var comments = db.comments.Where(...);
Pages.OrderByDescending(p=>(db.rating.Sum(r=>r.rate) + comments.Count()) * Math.Exp(-p.totalhoursago))
Linq is expecting Calculatesort to return a "queryable" expression in order to generate its own SQL.
In can embed your 'calculatesort' method in this lambda expression. (I replaced your variables with constants in order to compile in my environment)
public static void ComplexSort(IQueryable<string> Pages)
{
Pages.OrderByDescending(p =>
{
int rating = 99;//(from r in db.rating select r). sum();
int comments = 33;//query database for comments;
double timedecayfactor = Math.Exp(88);
return (rating + comments) * timedecayfactor;
});
}
Also, you can even try to run that in parallel (since .net 4.0) replacing the first line with
Pages.AsParallel().OrderByDescending(p =>
Yes, counting previous answers: the LINQ to SQL doesn't know how to translate CalculateSort method. You should convert LINQ to SQL to ordinary LINQ to Object before using custom method.
Try to use this in the way you call the CalculateSort by adding AsEnumerable:
Pages.AsEnumerable().OrderByDescending(o => CalculateSort(o.page));
Then you're fine to use the OrderByDescending extension method.
UPDATE:
LINQ to SQL will always translate the query in the code into Expression tree. It's quite almost the same concept as AST of any programming language. These expression trees are further translated into SQL expression specific to SQL Server's SQL, because currently LINQ to SQL only supports SQL Server 2005 and 2008.