Reputation: 13367
Ok, so a little background information. I had a method that looks like this:
private static IEnumerable<T> Yield<T>(this IEnumerable<T> models, int numberResults) => numberResults > 0 ? models.Take(numberResults) : models;
and this was working fine for my project. My project was not using Entity Framework. But now it has Identity Framework and thus it now uses Entity Framework.
I have the need now to list some Users but use that same function above. My method for that looks like this:
public async Task<List<UserViewModel>> ListByQueryAsync(string query, int yield)
{
// Put our query in lowercase
var loweredQuery = query.ToLower();
// Get our users and search
var users = base.Users.Where(m => m.UserName.ToLower().Contains(loweredQuery) || m.FirstName.ToLower().Contains(loweredQuery) || m.LastName.ToLower().Contains(loweredQuery)); //.Yield(yield);
// Return our users as a list
return await users.Select(m => UserFactory.Create(m)).ToListAsync();
}
the users variable is IQueryable which (if I am correct) implements IEnumerable and also, if it is converted to any concrete class it will execute the SQL. So I would like to get a list of users then use my static method to Yield the amount of results. At first I simply copied the method above and wrote this:
private static IQuerable<T> Yield<T>(this IQuerable<T> models, int numberResults) => numberResults > 0 ? models.Take(numberResults) : models;
This works fine, but being an advocate of DRY principles I decided that I have repeated the same method, so I figured because IQuerable implements IEumerable I would be able to write a private method that can handle bot public methods. So I wrote this:
public static class LinqExtensions
{
public static IEnumerable<T> Yield<T>(this IEnumerable<T> models, int numberResults) => models.YieldEnumerable(numberResults);
public static IQueryable<T> Yield<T>(this IQueryable<T> models, int numberResults) => models.YieldEnumerable(numberResults);
/// <summary>
/// Lists database entities by a number of results or lists them all
/// </summary>
/// <typeparam name="T">The generic entity to list</typeparam>
/// <param name="models">The query to yield</param>
/// <param name="numberResults">The number of results to yield</param>
/// <returns></returns>
private static IEnumerable<T> YieldEnumerable<T>(this IEnumerable<T> models, int numberResults) => numberResults > 0 ? models.Take(numberResults) : models;
}
And now I get an error stating :
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable' to 'System.Linq.IQuerable'.
I could cast the IQueryable as a list or something, but that would mean that my Yield method would actually execute the SQL and I don't want it to do that. Does anyone know how I can get around this issue?
Upvotes: 1
Views: 1930
Reputation: 127543
The reason it does not work is you actually are calling two different Take
methods from two different classes.
Your Yield<T>(this IEnumerable<T> models, int numberResults)
is calling public static IEnumerable<TSource> Take(this IEnumerable<TSource> source, int count)
from the System.Linq.Enumerable
class.
Your Yield<T>(this IQuerable<T> models, int numberResults)
is calling public static IQuerable<TSource> Take(this IQuerable<TSource> source, int count)
from the System.Linq.Queryable
class.
Notice the return type is different, the error you where getting was because your 2nd function needed to return a IQueryable<T>
but YieldEnumerable<T>
only returns a IEnumerable<T>
. And while you can implicitly cast a IQueryable<T>
to a IEnumerable<T>
you can't go the other way.
The work around you posted in your answer is forcing both methods go through Queryable.Take
, internally Queryable.Take
is creating a wrapper that ends up calling Enumerable.Take
for your IEnumeable
version.
However personally, I would just leave it as two methods, you are not violating DRY because you are calling two separate methods Enumerable.Yield
and Queryable.Yeild
. The point of DRY is to decrease code complexity, using the wrapper is just introducing overhead and increasing code complexity by introducing 50% more code (3 lines instead of 2).
Upvotes: 2
Reputation: 13367
I think I can solve this by just changing the methods around a bit like this:
public static class LinqExtensions
{
public static IEnumerable<T> Yield<T>(this IEnumerable<T> models, int numberResults) => models.AsQueryable().YieldQueryable(numberResults);
public static IQueryable<T> Yield<T>(this IQueryable<T> models, int numberResults) => models.YieldQueryable(numberResults);
/// <summary>
/// Lists database entities by a number of results or lists them all
/// </summary>
/// <typeparam name="T">The generic entity to list</typeparam>
/// <param name="models">The query to yield</param>
/// <param name="numberResults">The number of results to yield</param>
/// <returns></returns>
private static IQueryable<T> YieldQueryable<T>(this IQueryable<T> models, int numberResults) => numberResults > 0 ? models.Take(numberResults) : models;
}
It compiles and the users are still returned as IQuerable so it looks like it works.
Upvotes: 0