TruMan1
TruMan1

Reputation: 36098

How to implement Linq extensions in custom method?

I have a method where I use Linq to filter it and convert it to a List, but it is turned into a list to early for requested calls. See the custom method example here:

public IEnumerable<ItemModel> GetAll()
{
    var output = new List<ItemModel>();

    this.DataLayer.GetItems() //returns IQueryable<SomeWeirdItemModel>
        .Where(i => i.IsActive == true) 
        .ToList()
        .ForEach(i => output.Add(new ItemModel(i)));

    return output;
}

The problem is when I do this.GetAll().Where(i => i.StartDate >= DateTime.Now), my custom method is turning it into a List which retrieves EVERYTHING from the database, then my request filters by date. How do I get the called Linq and implement it into my custom method?

Something like this? this.DataLayer.GetNewsItems().Where(i => i.IsActive == true && (REQUESTED FILTER HERE?))

Upvotes: 0

Views: 400

Answers (1)

Erik Funkenbusch
Erik Funkenbusch

Reputation: 93444

You have several problems here.

The first is that ToList() will return all the items that match the Where clause from your database. You cannot do a ToList() and have the query NOT be executed immediately.

Second, you're returning IEnumerable<ItemModel>. IEnumerables also immediately execute the query. You have to return an IQueryable<ItemModel> if you want to add additional parameters to the returned type.

Third, it may be difficult to make this work with a Constructor argument, since this requires processing in code, rather than in the database. @dasblinkenlight's solution may work, although I can't test it. I'd assume he knows what he's talking about.

You need to change his code to this:

public IQueryable<ItemModel> GetAll()
{
    return this.DataLayer.GetNewsItems()
       .Where(i => i.IsActive) 
       .Select((v,i) => new ItemModel(i));
}

EDIT:

If you insist on using a constructor argument for your projected type, then you will have to compromise somewhere, such as passing the filter to your method.

Something like this:

public IEnumerable<ItemModel> GetItemsByDate(DateTime date)
{
    return this.DataLayer.GetNewsItems()
       .Where(i => i.IsActive && i.Date == date)
       .AsEnumerable()
       .Select(x => new ItemModel(x));
}

This will still execute the query when you call the method (you can't apply any more filters to the output and have it execute in the database), but it will only return the objects that match the IsActive and Date filters.

You could also apply an arbitrary expression like this:

public IEnumerable<ItemModel> GetItemsByDate(Expression<Func<SomeWeirdItemModel,bool>> filter)
{
    return this.DataLayer.GetNewsItems()
       .Where(i => i.IsActive && filter)
       .AsEnumerable()
       .Select(x => new ItemModel(x));
}

Then you can simply do this:

var items = DataLayer.GetAll(x => x.Date == date);
var others = DataLayer.GetAll(x => x.Date == date && x.Title.Length > 5 && x.Test = "X");
// etc..

Unfortunately, you will have to expose SomeWeirdModel in the parameter, because the expression has to filter on it, otherwise you would have to do a lot of work to try and translate the filter.

Upvotes: 2

Related Questions