EF Core - Using extension method inside Where - c#

I have the following extension method to calculate Product's average rating.
public static class Extensions
{
public static decimal CalculateAverageRating(this ICollection<ProductReview> productReviews)
{
// Calculate and return average rating
return averageRatings;
}
}
I want to use this method in an EF queryable where like this:
var products = _context.Products
.Include(pr => pr.ProductReviews)
.AsQueryable();
if(searchParams.Rating != 0)
products = products.Where(p => p.ProductReviews.CalculateAverageRating() == searchParams.Rating);
However im keep hitting the error "ArgumentException: Expression of type 'System.Collections.Generic.IAsyncEnumerable1[Products.Reviews.ProductReview]' cannot be used for parameter of type 'System.Collections.Generic.IEnumerable1[System.Object]' of method ".
Can we use extension method in a EF Where? Please advice me on this.

no problem with extension method in EF. The error is about wrong data type. So please try this way
public static class Extensions
{
public static decimal CalculateAverageRating(this IAsyncEnumerable<ProductReview> productReviews)
{
// Calculate and return average rating
return averageRatings;
}
}
var products = _context.Products
.Include(pr => pr.ProductReviews)
.AsQueryable().ToAsyncEnumerable();
if(searchParams.Rating != 0)
products = products.Where(p => p.ProductReviews.CalculateAverageRating() == searchParams.Rating);
PS : I have not checked above code

First of all sorry for coming late to the party...
But like Gert Arnold said, this will all be done in memory.
So EF will fetch the entire products table in memory with the product reviews, this can be catastrophical for performance.
What you might want to do is (2 is the better way):
If you really want to keep using your extension method, project the needed proprties from the product and product reviews into a vm and then do the filtering.
Just use the average method that sql offers you

Related

Optimal LINQ filtering method

I'm filtering the results of a list of items in LINQ, I have seen two methods of doing it and wondered which (if any) is better. One is the method I came up with after playing around with the Intellisense, the other is from the ASP.NET MVC tutorial (found here)
My method
// GET: ProductVersions
public ActionResult Index(string productName)
{
var productVersions = db.ProductVersions.Include(p => p.LicenceVersion).Include(p => p.Product);
if (!string.IsNullOrEmpty(productName))
{
productVersions = productVersions.Where(s => s.Product.Name == productName);
}
return View(productVersions.ToList());
}
Tutorial Method
public ActionResult Index(string movieGenre)
{
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
var movies = from m in db.Movies
select m;
if (!string.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
return View(movies);
}
My questions
Is there a notable difference in performance, particularly as the second option is quite verbose
Is there a stylistic convention that I am missing by using my method
Is there any other possible advantage to using the second method
~Edit~
Turns out I need the ViewBag data in order to be able to populate a dropdown filter on the front end (more's the pity), so my actual code worked out as follows:
// GET: ProductVersions
public ActionResult Index(string productName)
{
//Get list of product names to filter by
var ProductLst = new List<string>();
var ProductQry = from pv in db.ProductVersions
orderby pv.Product.Name
select pv.Product.Name;
ProductLst.AddRange(ProductQry.Distinct());
ViewBag.productName = new SelectList(ProductLst);
//Populate product versions
var productVersions = db.ProductVersions.Include(p => p.LicenceVersion).Include(p => p.Product);
//Filter by product name
if (!string.IsNullOrEmpty(productName))
{
productVersions = productVersions.Where(s => s.Product.Name == productName);
}
return View(productVersions.ToList());
}
It is only the last part of the example code that is comparable to your question - the genre list is something else in the UI that isn't present in your code, and that's fine. So all we are comparing is:
var productVersions = db.ProductVersions.Include(p => p.LicenceVersion)
.Include(p => p.Product);
if (!string.IsNullOrEmpty(productName))
{
productVersions = productVersions.Where(s => s.Product.Name == productName);
}
return View(productVersions.ToList());
vs
var movies = from m in db.Movies
select m;
if (!string.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
return View(movies);
These are virtually identical - the main difference is the extra includes in your code, which is fine if you need them.
They are so comparable that there is nothing relevant to talk about in terms of comparisons.
Personally I prefer the ToList() in your example as it forces the data to materialize in the controller rather than the view. A counter to that is that having the view have a queryable allows the view to compose it further. I don't want my view composing queries, but that is a stylistic point.
The code samples you provided have quite some differences, but assuming you are asking about extension methods syntax vs query syntax, the answers I believe are as follows:
There is no difference in performance. The compiler treats the samples identically.
You are not missing any stylistic convention. I usually find chaining extension methods to be more readable and maintainable
There can be a case made for using query syntax when you want to leverage multiple range variables. Check out this answer:
LINQ - Fluent and Query Expression - Is there any benefit(s) of one over other?
I understand that you use Eager Loading. I think EagerLoading is not necessary if your model isn't so complex.
For this reason use Layz loading; Also I try to refactor your code. Look at my code style and codeLine count :) This can help for you especially huge class :) H
http://www.entityframeworktutorial.net/EntityFramework4.3/lazy-loading-with-dbcontext.aspx
At the below link you can get detailed Info.
The sample class can translate linke this also
public ActionResult Index(string productName)
{
if(string.IsNullOrEmpty(productName)) return View(new List<ProductVersions>());
return View(db.ProductVersions.Where(s => s.Product.Name == productName).ToList());
}
Lastly your comprission is wrong between "your exp and tutorial". Because ViewModel used In TutorialExp. This is usefull to prevent directly DbCon object using by Client's. In my opinion
using(var db=new DbEntities())
is required for this tutorial.

nopCommerce IProductService returns IList instead of IQueryable

The ProductService class in nopCommerce has a method GetAllProducts which returns an IList containing all products.
Wouldn't it make far more sense to return the IQueryable instead? That way I could further filter the query myself.
See code below --
public virtual IList<Product> GetAllProducts(bool showHidden = false)
{
var query = from p in _productRepository.Table
orderby p.Name
where (showHidden || p.Published) &&
!p.Deleted
select p;
var products = query.ToList();
return products;
}
Why convert it to a list when the IQueryable is perfectly sufficient? I cannot understand the rationale behind this.
Given this particular limitation, is there a way to work around this without modifying the Nop.Services code?
For example, I'd like to run a query to get all products created within the last 5 days. I see no way of doing this through the IProductService interface.
Is there a simple way of doing something like this?
It's possible.
Example:
var products = _productService.GetAllProducts().Where(p => p.CreatedOnUtc > DateTime.UtcNow.AddDays(-5));

LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression

I'm migrating some stuff from one mysql server to a sql server but i can't figure out how to make this code work:
using (var context = new Context())
{
...
foreach (var item in collection)
{
IQueryable<entity> pages = from p in context.pages
where p.Serial == item.Key.ToString()
select p;
foreach (var page in pages)
{
DataManager.AddPageToDocument(page, item.Value);
}
}
Console.WriteLine("Done!");
Console.Read();
}
When it enters into the second foreach (var page in pages) it throws an exception saying:
LINQ to Entities does not recognize the method 'System.String
ToString()' method, and this method cannot be translated into a store
expression.
Anyone know why this happens?
Just save the string to a temp variable and then use that in your expression:
var strItem = item.Key.ToString();
IQueryable<entity> pages = from p in context.pages
where p.Serial == strItem
select p;
The problem arises because ToString() isn't really executed, it is turned into a MethodGroup and then parsed and translated to SQL. Since there is no ToString() equivalent, the expression fails.
Note:
Make sure you also check out Alex's answer regarding the SqlFunctions helper class that was added later. In many cases it can eliminate the need for the temporary variable.
As others have answered, this breaks because .ToString fails to translate to relevant SQL on the way into the database.
However, Microsoft provides the SqlFunctions class that is a collection of methods that can be used in situations like this.
For this case, what you are looking for here is SqlFunctions.StringConvert:
from p in context.pages
where p.Serial == SqlFunctions.StringConvert((double)item.Key.Id)
select p;
Good when the solution with temporary variables is not desirable for whatever reasons.
Similar to SqlFunctions you also have the EntityFunctions (with EF6 obsoleted by DbFunctions) that provides a different set of functions that also are data source agnostic (not limited to e.g. SQL).
The problem is that you are calling ToString in a LINQ to Entities query. That means the parser is trying to convert the ToString call into its equivalent SQL (which isn't possible...hence the exception).
All you have to do is move the ToString call to a separate line:
var keyString = item.Key.ToString();
var pages = from p in context.entities
where p.Serial == keyString
select p;
Cast table to Enumerable, then you call LINQ methods with using ToString() method inside:
var example = contex.table_name.AsEnumerable()
.Select(x => new {Date = x.date.ToString("M/d/yyyy")...)
But be careful, when you calling AsEnumerable or ToList methods because you will request all data from all entity before this method. In my case above I read all table_name rows by one request.
Had a similar problem.
Solved it by calling ToList() on the entity collection and querying the list.
If the collection is small this is an option.
IQueryable<entity> pages = context.pages.ToList().Where(p=>p.serial == item.Key.ToString())
Hope this helps.
Upgrading to Entity Framework Version 6.2.0 worked for me.
I was previously on Version 6.0.0.
Hope this helps,
Change it like this and it should work:
var key = item.Key.ToString();
IQueryable<entity> pages = from p in context.pages
where p.Serial == key
select p;
The reason why the exception is not thrown in the line the LINQ query is declared but in the line of the foreach is the deferred execution feature, i.e. the LINQ query is not executed until you try to access the result. And this happens in the foreach and not earlier.
If you really want to type ToString inside your query, you could write an expression tree visitor that rewrites the call to ToString with a call to the appropriate StringConvert function:
using System.Linq;
using System.Data.Entity.SqlServer;
using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;
using System;
namespace ToStringRewriting {
class ToStringRewriter : ExpressionVisitor {
static MethodInfo stringConvertMethodInfo = typeof(SqlFunctions).GetMethods()
.Single(x => x.Name == "StringConvert" && x.GetParameters()[0].ParameterType == typeof(decimal?));
protected override Expression VisitMethodCall(MethodCallExpression node) {
var method = node.Method;
if (method.Name=="ToString") {
if (node.Object.GetType() == typeof(string)) { return node.Object; }
node = Call(stringConvertMethodInfo, Convert(node.Object, typeof(decimal?));
}
return base.VisitMethodCall(node);
}
}
class Person {
string Name { get; set; }
long SocialSecurityNumber { get; set; }
}
class Program {
void Main() {
Expression<Func<Person, Boolean>> expr = x => x.ToString().Length > 1;
var rewriter = new ToStringRewriter();
var finalExpression = rewriter.Visit(expr);
var dcx = new MyDataContext();
var query = dcx.Persons.Where(finalExpression);
}
}
}
In MVC, assume you are searching record(s) based on your requirement or information.
It is working properly.
[HttpPost]
[ActionName("Index")]
public ActionResult SearchRecord(FormCollection formcollection)
{
EmployeeContext employeeContext = new EmployeeContext();
string searchby=formcollection["SearchBy"];
string value=formcollection["Value"];
if (formcollection["SearchBy"] == "Gender")
{
List<MvcApplication1.Models.Employee> emplist = employeeContext.Employees.Where(x => x.Gender == value).ToList();
return View("Index", emplist);
}
else
{
List<MvcApplication1.Models.Employee> emplist = employeeContext.Employees.Where(x => x.Name == value).ToList();
return View("Index", emplist);
}
}
I got the same error in this case:
var result = Db.SystemLog
.Where(log =>
eventTypeValues.Contains(log.EventType)
&& (
search.Contains(log.Id.ToString())
|| log.Message.Contains(search)
|| log.PayLoad.Contains(search)
|| log.Timestamp.ToString(CultureInfo.CurrentUICulture).Contains(search)
)
)
.OrderByDescending(log => log.Id)
.Select(r => r);
After spending way too much time debugging, I figured out that error appeared in the logic expression.
The first line search.Contains(log.Id.ToString()) does work fine, but the last line that deals with a DateTime object made it fail miserably:
|| log.Timestamp.ToString(CultureInfo.CurrentUICulture).Contains(search)
Remove the problematic line and problem solved.
I do not fully understand why, but it seems as ToString() is a LINQ expression for strings, but not for Entities. LINQ for Entities deals with database queries like SQL, and SQL has no notion of ToString(). As such, we can not throw ToString() into a .Where() clause.
But how then does the first line work? Instead of ToString(), SQL have CAST and CONVERT, so my best guess so far is that linq for entities uses that in some simple cases. DateTime objects are not always found to be so simple...
My problem was that I had a 'text' data type for this column (due to a migration from sqlite).
Solution: just change the data type to 'nvarchar()' and regenerate the table.
Then Linq accepts the string comparison.
I am working on retiring Telerik Open Access and replacing it with Entity Framework 4.0. I came across same issue that telerik:GridBoundColumn filtering stopped working.
I find out that its not working only on System.String DataTypes. So I found this thread and solved it by just using .List() at the end of my Linq query as follows:
var x = (from y in db.Tables
orderby y.ColumnId descending
select new
{
y.FileName,
y.FileSource,
y.FileType,
FileDepartment = "Claims"
}).ToList();
Just turn the LINQ to Entity query into a LINQ to Objects query (e.g. call ToArray) anytime you need to use a method call in your LINQ query.

Can someone explain why these two linq queries return different results?

I have two linq (to EF4) queries, which return different results. The first query contains the correct results, but is not formatted/projected right.
the second query is what i want but it missing some data.
Schema
alt text http://img220.imageshack.us/img220/9678/schema.png
Query 1
var xxxx = (from cp in _connectedClientRepository
.GetConnectedClients(new[] { "LogEntry", "LogEntry.GameFile" })
.AsExpandable()
.Where(predicate)
select cp)
.ToList();
alt text http://img231.imageshack.us/img231/6541/image2ys.png
Notice the property GameFile . It is not null. This is great :) Notice the linq query? I'm eager loading a LogEntry and then eager loading a GameFile (for each eager loaded LogEntry).
This is what i'm after -> for each LogEntry that is eager loaded, please eager load the GameFile. But this projection result is wrong...
Ok.. next...
Query 2
var yyy = (from cp in _connectedClientRepository
.GetConnectedClients(new[] { "LogEntry", "LogEntry.GameFile" })
.AsExpandable()
.Where(predicate)
select cp.LogEntry)
.ToList();
NOTE: the image above has a typo in it ... please note the include associations typed code is correct (ie. LogEntry.GameFile) while the image has it typo'd.
Correct projection now -> all LogEntries results. But notice how the GameFile property is now null? I'm not sure why :( I thought i correctly eager loaded the correct chain. So this is the correct projection but with incorrect results.
Obligatory Repository code.
public IQueryable<ConnectedClient> GetConnectedClients(
string[] includeAssociations)
{
return Context.ConnectedClients
.IncludeAssociations(includeAssociations)
.AsQueryable();
}
public static class Extensions
{
public static IQueryable<T> IncludeAssociation<T>(
this IQueryable<T> source, string includeAssociation)
{
if (!string.IsNullOrEmpty(includeAssociation))
{
var objectQuery = source as ObjectQuery<T>;
if (objectQuery != null)
{
return objectQuery.Include(includeAssociation);
}
}
return source;
}
public static IQueryable<T> IncludeAssociations<T>(
this IQueryable<T> source, params string[] includeAssociations)
{
if (includeAssociations != null)
{
foreach (string association in includeAssociations)
{
source = source.IncludeAssociation(association);
}
}
return source;
}
}
Updates
1 : Fixed some typo's in noticed in the code samples.
2 : Added repository code to help anyone who is confused.
I suspect Craig Stuntz' suggestion may work, but if it doesn't, the following should certainly work:
var xxxx =_connectedClientRepository
.GetConnectedClients(new[] { "LogEntry", "LogEntry.GameFile" })
.AsExpandable()
.Where(predicate)
.ToList() // execute query
.Select(cp => cp.LogEntry); // use linq-to-objects to project the result
Include() works on the query results, rather than the intermediate queries. You can read more about Include() in this post. So one solution is to apply the Include() to the whole query, like this:
var q = ((from cp in _connectedClientRepository.GetConnectedClients()
.AsExpandable()
.Where(predicate)
select cp.LogEntry)
as ObjectQuery).Include("GameFile").ToList();
That will probably work, but it's ugly. Can we do better?
I can think of two ways around this issue. Mostly, it depends upon whether or not you actually need entity types returned. I can't say whether this is the case without seeing the rest of your code. Generally, you need to return entity types when you are going to update (or otherwise modify) them. If you are selecting for display or calculation purposes, it's often a better strategy to return POCOs instead of entity types. You can do this with projection, and of course it works in EF 1. In this case, you would change your repository method to return POCO types:
public IQueryable<ClientInfo> GetConnectedClients()
{
return from cp in _context.Clients
where // ...
select new ClientInfo
{
Id = cp.Id,
ClientName = cp.ClientName,
LogEntry = new LogEntryInfo
{
LogEntryId = cp.LogEntry.LogEntryId,
GameFile = new GameFileInfo
{
GameFileId = cp.LogEntry.GameFile.GameFileId,
// etc.
},
// etc.
},
// etc.
};
}
Note that when you use projection there is no eager loading, no lazy loading, and no explicit loading. There is only your intention, expressed as a query. The LINQ provider will figure out what you need, even if you further compose the query outside the repository!
On the other hand, you might need to return entity types instead of POCOs, because you intend to update them. In that case, I would write a separate repository method for the LogEntries, as Tomas suggested. But I would only do this if I intended to update them, and I might write it as an update method, rather than a "Get" method.
It seems you need one more repository method, that will do this for you; _connectedClientsRepository.GetLogEntriesOfConnectedClients().

How To Define The Return Type In A Function With LINQ?

I would like to know how to define a returntype in a function in
following situation.
I've got a products and I was returning all information or one product at a time.
as you can see in my function defined below.
public static Products GetProducts(int pid)
{
var pro = from p in context.Products
select p;
if(pid > 0)
pro = pro.where(p => p.ProductID ==pid)
return (Products)p;
}
the problem is its give me casting error. as you can see what i want to achieve is based on my parameter its give me a result set. some time bunch of products & some time single product. i m new to linq so any help would be appreciated.
The error is Unable to cast object of type 'System.Data.Objects.ObjectQuery`1[TTDCore.Theatres]' to type 'TTDCore.Theatres'
when i m binding it to gridview. here is a code
Products p = Class1.GetProducts(0);
GridView1.DataSource = p;
GridView1.DataBind();
You want to return IEnumerable<Product>, which represents an iterable (or enumerable) of objects of type Product. LINQ in general is all based around this generic type, so it's generally what you want to return as a result of a query.
I believe your code should be fixed to become something like this:
public static IEnumerable<Products> GetProducts(int pid)
{
var pro = from p in context.Products
select p;
if(pid > 0)
pro = pro.Where(p => p.ProductID == pid)
return pro;
}
Let me know if you meant something else in your question. I wasn't totally sure what precisely you were searching for.
I like to be explicit with Linq queries and lambdas. I suggest defining your function as List<Product> (or IEnumerable<Product>), then adding .ToList() to your where statement. I assume that the Products type is some sort of collection?

Categories