r3plica
r3plica

Reputation: 13367

Creating a generic method in IEnumerable

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

Answers (2)

Scott Chamberlain
Scott Chamberlain

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

r3plica
r3plica

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

Related Questions